aboutsummaryrefslogtreecommitdiff
path: root/ref
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-04-02 06:23:30 +0000
committerGravatar Runxi Yu2026-04-02 06:28:39 +0000
commita041d523de389b65b98a5373a8034041db2a8d83 (patch)
tree7b423dc735f463be616045f2c3c2095a7737aca7 /ref
parentresearch: Add dynamic pack resources (diff)
signatureNo signature
*: Remove
Diffstat (limited to 'ref')
-rw-r--r--ref/detached.go22
-rw-r--r--ref/doc.go5
-rw-r--r--ref/name/branch.go25
-rw-r--r--ref/name/component.go88
-rw-r--r--ref/name/current.go5
-rw-r--r--ref/name/disposition.go20
-rw-r--r--ref/name/doc.go7
-rw-r--r--ref/name/errors.go14
-rw-r--r--ref/name/flags.go6
-rw-r--r--ref/name/length.go11
-rw-r--r--ref/name/lock.go3
-rw-r--r--ref/name/normalize.go53
-rw-r--r--ref/name/options.go30
-rw-r--r--ref/name/pseudo.go11
-rw-r--r--ref/name/refname_test.go622
-rw-r--r--ref/name/root.go21
-rw-r--r--ref/name/root_syntax.go13
-rw-r--r--ref/name/safe.go31
-rw-r--r--ref/name/sanitize.go19
-rw-r--r--ref/name/slashes.go26
-rw-r--r--ref/name/tag.go20
-rw-r--r--ref/name/update.go56
-rw-r--r--ref/name/utils.go22
-rw-r--r--ref/name/validate.go65
-rw-r--r--ref/name/worktree.go75
-rw-r--r--ref/ref.go9
-rw-r--r--ref/store/batch.go69
-rw-r--r--ref/store/batch_store.go9
-rw-r--r--ref/store/chain/chain.go12
-rw-r--r--ref/store/chain/close.go6
-rw-r--r--ref/store/chain/list.go40
-rw-r--r--ref/store/chain/new.go14
-rw-r--r--ref/store/chain/resolve.go64
-rw-r--r--ref/store/doc.go14
-rw-r--r--ref/store/errors.go7
-rw-r--r--ref/store/files/batch.go11
-rw-r--r--ref/store/files/batch_abort.go6
-rw-r--r--ref/store/files/batch_apply.go129
-rw-r--r--ref/store/files/batch_begin.go13
-rw-r--r--ref/store/files/batch_queue.go12
-rw-r--r--ref/store/files/batch_queue_ops.go43
-rw-r--r--ref/store/files/batch_rejection.go28
-rw-r--r--ref/store/files/batch_result_error.go21
-rw-r--r--ref/store/files/batch_test.go129
-rw-r--r--ref/store/files/broken_ref_error.go16
-rw-r--r--ref/store/files/close.go8
-rw-r--r--ref/store/files/helpers_test.go150
-rw-r--r--ref/store/files/new.go31
-rw-r--r--ref/store/files/packed_delete_test.go292
-rw-r--r--ref/store/files/packed_parse.go113
-rw-r--r--ref/store/files/packed_read.go35
-rw-r--r--ref/store/files/packed_refs.go10
-rw-r--r--ref/store/files/read_list.go76
-rw-r--r--ref/store/files/read_list_collect.go78
-rw-r--r--ref/store/files/read_loose.go48
-rw-r--r--ref/store/files/read_resolve.go41
-rw-r--r--ref/store/files/read_resolve_fully.go42
-rw-r--r--ref/store/files/resolve_list_test.go269
-rw-r--r--ref/store/files/root_for.go13
-rw-r--r--ref/store/files/root_kind.go8
-rw-r--r--ref/store/files/root_loose_path.go24
-rw-r--r--ref/store/files/root_open_common.go31
-rw-r--r--ref/store/files/store.go31
-rw-r--r--ref/store/files/transaction.go13
-rw-r--r--ref/store/files/transaction_abort.go4
-rw-r--r--ref/store/files/transaction_begin.go13
-rw-r--r--ref/store/files/transaction_commit.go13
-rw-r--r--ref/store/files/transaction_dirs_test.go220
-rw-r--r--ref/store/files/transaction_names_test.go188
-rw-r--r--ref/store/files/transaction_pseudoref_test.go106
-rw-r--r--ref/store/files/transaction_queue.go12
-rw-r--r--ref/store/files/transaction_queue_ops.go43
-rw-r--r--ref/store/files/transaction_symbolic_test.go154
-rw-r--r--ref/store/files/transaction_update_test.go178
-rw-r--r--ref/store/files/trim.go10
-rw-r--r--ref/store/files/update_cleanup.go39
-rw-r--r--ref/store/files/update_cleanup_parents.go30
-rw-r--r--ref/store/files/update_commit.go25
-rw-r--r--ref/store/files/update_commit_delete.go25
-rw-r--r--ref/store/files/update_dir_tree.go59
-rw-r--r--ref/store/files/update_direct_read.go76
-rw-r--r--ref/store/files/update_direct_ref.go20
-rw-r--r--ref/store/files/update_error.go28
-rw-r--r--ref/store/files/update_executor.go5
-rw-r--r--ref/store/files/update_kind.go14
-rw-r--r--ref/store/files/update_lock.go25
-rw-r--r--ref/store/files/update_lock_packed.go44
-rw-r--r--ref/store/files/update_operation_prepared.go6
-rw-r--r--ref/store/files/update_operation_queue.go12
-rw-r--r--ref/store/files/update_path.go28
-rw-r--r--ref/store/files/update_prepare.go48
-rw-r--r--ref/store/files/update_prepare_lock.go29
-rw-r--r--ref/store/files/update_prepare_resolve.go43
-rw-r--r--ref/store/files/update_prepare_verify.go21
-rw-r--r--ref/store/files/update_resolve_target.go21
-rw-r--r--ref/store/files/update_resolve_target_ordinary.go48
-rw-r--r--ref/store/files/update_target_resolved.go7
-rw-r--r--ref/store/files/update_validate.go66
-rw-r--r--ref/store/files/update_verify_current.go60
-rw-r--r--ref/store/files/update_verify_refnames.go41
-rw-r--r--ref/store/files/update_visible_names.go29
-rw-r--r--ref/store/files/update_write_loose.go59
-rw-r--r--ref/store/files/update_write_packed_refs.go98
-rw-r--r--ref/store/files/worktree_test.go206
-rw-r--r--ref/store/reading.go34
-rw-r--r--ref/store/transaction.go52
-rw-r--r--ref/store/transactional_store.go13
-rw-r--r--ref/store/update_errors.go110
-rw-r--r--ref/symbolic.go14
109 files changed, 0 insertions, 5611 deletions
diff --git a/ref/detached.go b/ref/detached.go
deleted file mode 100644
index e9b30906..00000000
--- a/ref/detached.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package ref
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Detached points directly to an object ID.
-type Detached struct {
- RefName string
- ID objectid.ObjectID
-
- // Peeled is the peeled target when available (for annotated tags).
- //
- // This field is optional backend-provided metadata. Backends that do not
- // have peel metadata available may leave it nil.
- Peeled *objectid.ObjectID
-}
-
-// Name returns the fully-qualified reference name.
-func (ref Detached) Name() string {
- return ref.RefName
-}
-
-func (Detached) isRef() {}
diff --git a/ref/doc.go b/ref/doc.go
deleted file mode 100644
index 610c3013..00000000
--- a/ref/doc.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// Package ref provides Git reference values.
-//
-// A reference is either [Detached], which points directly to an object ID, or
-// [Symbolic], which points to another reference name.
-package ref
diff --git a/ref/name/branch.go b/ref/name/branch.go
deleted file mode 100644
index 274a95e3..00000000
--- a/ref/name/branch.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package refname
-
-import "strings"
-
-// Branch checks one branch shorthand and returns its fully-qualified
-// refs/heads/... name.
-//
-// Unlike Git in-repository branch parsing, this helper does not expand @{-n}.
-func Branch(name string) (string, error) {
- full := "refs/heads/" + name
- if strings.HasPrefix(name, "-") || full == "refs/heads/HEAD" {
- return "", &NameError{Name: name, Reason: "invalid branch name"}
- }
-
- err := validate(full, 0)
- if err != nil {
- return "", err
- }
-
- if strings.HasPrefix(name, "refs/") {
- return name, nil
- }
-
- return full, nil
-}
diff --git a/ref/name/component.go b/ref/name/component.go
deleted file mode 100644
index f5adba46..00000000
--- a/ref/name/component.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package refname
-
-import "strings"
-
-func checkRefnameComponent(name string, flags *int, sanitized *strings.Builder, fullName string) (int, error) {
- var last byte
-
- componentStart := sanitizedLen(sanitized)
-
- for i := range len(name) {
- ch := name[i]
- disp := refnameDisposition(ch)
-
- if sanitized != nil && disp != 1 {
- sanitized.WriteByte(ch)
- }
-
- switch disp {
- case 1:
- goto out
- case 2:
- if last == '.' {
- if sanitized != nil {
- truncateBuilder(sanitized, sanitized.Len()-1)
- } else {
- return 0, &NameError{Name: fullName, Reason: "name contains '..'"}
- }
- }
- case 3:
- if last == '@' {
- if sanitized != nil {
- overwriteLastByte(sanitized, '-')
- } else {
- return 0, &NameError{Name: fullName, Reason: "name contains '@{'"}
- }
- }
- case 4:
- if sanitized != nil {
- overwriteLastByte(sanitized, '-')
- } else {
- return 0, &NameError{Name: fullName, Reason: "name contains one forbidden character"}
- }
- case 5:
- if *flags&refnameRefspecPattern == 0 {
- if sanitized != nil {
- overwriteLastByte(sanitized, '-')
- } else {
- return 0, &NameError{Name: fullName, Reason: "name contains '*'"}
- }
- }
-
- *flags &^= refnameRefspecPattern
- }
-
- last = ch
- }
-
-out:
- componentLen := strings.IndexByte(name, '/')
-
- if componentLen < 0 {
- componentLen = len(name)
- }
-
- if componentLen == 0 {
- return 0, nil
- }
-
- if name[0] == '.' {
- if sanitized != nil {
- overwriteBuilderAt(sanitized, componentStart, '-')
- } else {
- return 0, &NameError{Name: fullName, Reason: "component starts with '.'"}
- }
- }
-
- if componentLen >= len(lockSuffix) && name[componentLen-len(lockSuffix):componentLen] == lockSuffix {
- if sanitized == nil {
- return 0, &NameError{Name: fullName, Reason: "component ends with .lock"}
- }
-
- for strings.HasSuffix(sanitized.String(), lockSuffix) {
- truncateBuilder(sanitized, sanitized.Len()-len(lockSuffix))
- }
- }
-
- return componentLen, nil
-}
diff --git a/ref/name/current.go b/ref/name/current.go
deleted file mode 100644
index 3a5394cc..00000000
--- a/ref/name/current.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package refname
-
-func isCurrentWorktreeRef(name string) bool {
- return IsRootSyntax(name) || IsPerWorktree(name)
-}
diff --git a/ref/name/disposition.go b/ref/name/disposition.go
deleted file mode 100644
index 5153e633..00000000
--- a/ref/name/disposition.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package refname
-
-func refnameDisposition(ch byte) byte {
- switch {
- case ch == '/':
- return 1
- case ch == '.':
- return 2
- case ch == '{':
- return 3
- case ch == '*':
- return 5
- case ch < 0x20 || ch == 0x7f:
- return 4
- case ch == ':' || ch == '?' || ch == '[' || ch == '\\' || ch == '^' || ch == '~' || ch == ' ' || ch == '\t':
- return 4
- default:
- return 0
- }
-}
diff --git a/ref/name/doc.go b/ref/name/doc.go
deleted file mode 100644
index 48d06aac..00000000
--- a/ref/name/doc.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Package refname provides Git reference-name validation, normalization, and
-// classification helpers.
-//
-// It includes branch and tag abbreviation expansion, worktree-qualified refs,
-// update validation, and the distinction between ordinary refs, root refs,
-// pseudo-refs, and filesystem-safe names.
-package refname
diff --git a/ref/name/errors.go b/ref/name/errors.go
deleted file mode 100644
index e39bc73b..00000000
--- a/ref/name/errors.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package refname
-
-import "fmt"
-
-// NameError reports one invalid reference name.
-type NameError struct {
- Name string
- Reason string
-}
-
-// Error implements error.
-func (err *NameError) Error() string {
- return fmt.Sprintf("ref: invalid name %q: %s", err.Name, err.Reason)
-}
diff --git a/ref/name/flags.go b/ref/name/flags.go
deleted file mode 100644
index 72f0a58f..00000000
--- a/ref/name/flags.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package refname
-
-const (
- refnameAllowOneLevel = 1 << iota
- refnameRefspecPattern
-)
diff --git a/ref/name/length.go b/ref/name/length.go
deleted file mode 100644
index 94c0322b..00000000
--- a/ref/name/length.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package refname
-
-import "strings"
-
-func sanitizedLen(builder *strings.Builder) int {
- if builder == nil {
- return 0
- }
-
- return builder.Len()
-}
diff --git a/ref/name/lock.go b/ref/name/lock.go
deleted file mode 100644
index 33db902f..00000000
--- a/ref/name/lock.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package refname
-
-const lockSuffix = ".lock"
diff --git a/ref/name/normalize.go b/ref/name/normalize.go
deleted file mode 100644
index 9cbe7126..00000000
--- a/ref/name/normalize.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package refname
-
-import "strings"
-
-// Normalize collapses slashes according to what Git wants
-// then validates the normalized name.
-func Normalize(name string, options Options) (string, error) {
- normalized := collapseSlashes(name)
-
- err := validate(normalized, options.flags())
- if err != nil {
- return "", err
- }
-
- return normalized, nil
-}
-
-func normalizeRefPath(path string) (string, bool) {
- components := make([]string, 0, strings.Count(path, "/")+1)
- i := 0
-
- for i < len(path) {
- for i < len(path) && path[i] == '/' {
- i++
- }
-
- if i == len(path) {
- break
- }
-
- j := i
- for j < len(path) && path[j] != '/' {
- j++
- }
-
- component := path[i:j]
- switch component {
- case ".":
- case "..":
- if len(components) == 0 {
- return "", false
- }
-
- components = components[:len(components)-1]
- default:
- components = append(components, component)
- }
-
- i = j
- }
-
- return strings.Join(components, "/"), true
-}
diff --git a/ref/name/options.go b/ref/name/options.go
deleted file mode 100644
index 5ae81541..00000000
--- a/ref/name/options.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package refname
-
-import "fmt"
-
-// Options controls Git refname validation.
-type Options struct {
- // AllowOneLevel permits one-component refnames like HEAD.
- AllowOneLevel bool
-
- // RefspecPattern permits one '*' anywhere in the refname.
- RefspecPattern bool
-}
-
-// String returns one stable text form of the options.
-func (options Options) String() string {
- return fmt.Sprintf("allow_onelevel=%t,refspec_pattern=%t", options.AllowOneLevel, options.RefspecPattern)
-}
-
-func (options Options) flags() int {
- var flags int
- if options.AllowOneLevel {
- flags |= refnameAllowOneLevel
- }
-
- if options.RefspecPattern {
- flags |= refnameRefspecPattern
- }
-
- return flags
-}
diff --git a/ref/name/pseudo.go b/ref/name/pseudo.go
deleted file mode 100644
index f0ad1ae8..00000000
--- a/ref/name/pseudo.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package refname
-
-// IsPseudo reports whether name is one Git pseudo-ref.
-func IsPseudo(name string) bool {
- switch name {
- case "FETCH_HEAD", "MERGE_HEAD":
- return true
- default:
- return false
- }
-}
diff --git a/ref/name/refname_test.go b/ref/name/refname_test.go
deleted file mode 100644
index a37314f4..00000000
--- a/ref/name/refname_test.go
+++ /dev/null
@@ -1,622 +0,0 @@
-package refname_test
-
-import (
- "context"
- "os/exec"
- "strings"
- "testing"
-
- "codeberg.org/lindenii/furgit/ref/name"
-)
-
-func TestValidateNameAgainstGit(t *testing.T) {
- t.Parallel()
-
- type testCase struct {
- name string
- opts refname.Options
- }
-
- tests := []testCase{
- {name: ""},
- {name: "/"},
- {name: "/", opts: refname.Options{AllowOneLevel: true}},
- {name: "foo/bar/baz"},
- {name: "refs/heads/main"},
- {name: "refs/tags/v1.0.0"},
- {name: "refs///heads/foo"},
- {name: "heads/foo/"},
- {name: "/heads/foo"},
- {name: "///heads/foo"},
- {name: "./foo"},
- {name: "./foo/bar"},
- {name: "foo/./bar"},
- {name: "foo/bar/."},
- {name: ".refs/foo"},
- {name: "refs/heads/foo."},
- {name: "HEAD"},
- {name: "HEAD", opts: refname.Options{AllowOneLevel: true}},
- {name: "refs/heads/.main"},
- {name: "heads/foo..bar"},
- {name: "refs/heads/main.lock"},
- {name: "heads///foo.lock"},
- {name: "refs/heads/foo..bar"},
- {name: "refs/heads/foo bar"},
- {name: "refs/heads/foo@{bar"},
- {name: "heads/foo?bar"},
- {name: "foo./bar"},
- {name: "foo.lock/bar"},
- {name: "foo.lock///bar"},
- {name: "heads/foo@bar"},
- {name: "heads/foo\\bar"},
- {name: "heads/foo\tbar"},
- {name: "heads/foo\x7fbar"},
- {name: "heads/fu\xC3\x9F"},
- {name: "heads/*foo/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "heads/foo*/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "heads/f*o/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "heads/f*o*/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "heads/foo*/bar*", opts: refname.Options{RefspecPattern: true}},
- {name: "refs/heads/foo/bar."},
- {name: "refs//heads///main"},
- {name: "foo"},
- {name: "foo", opts: refname.Options{AllowOneLevel: true}},
- {name: "foo", opts: refname.Options{RefspecPattern: true}},
- {name: "foo", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "foo/bar"},
- {name: "foo/bar", opts: refname.Options{AllowOneLevel: true}},
- {name: "foo/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "foo/bar", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "refs/heads/*"},
- {name: "refs/heads/*", opts: refname.Options{RefspecPattern: true}},
- {name: "refs/heads/feature*branch", opts: refname.Options{RefspecPattern: true}},
- {name: "refs/heads/foo*bar*baz", opts: refname.Options{RefspecPattern: true}},
- {name: "foo/*"},
- {name: "foo/*", opts: refname.Options{RefspecPattern: true}},
- {name: "foo/*", opts: refname.Options{AllowOneLevel: true}},
- {name: "foo/*", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "*/foo"},
- {name: "*/foo", opts: refname.Options{RefspecPattern: true}},
- {name: "*/foo", opts: refname.Options{AllowOneLevel: true}},
- {name: "*/foo", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "foo/*/bar"},
- {name: "foo/*/bar", opts: refname.Options{RefspecPattern: true}},
- {name: "foo/*/bar", opts: refname.Options{AllowOneLevel: true}},
- {name: "foo/*/bar", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "*"},
- {name: "*", opts: refname.Options{AllowOneLevel: true}},
- {name: "*", opts: refname.Options{RefspecPattern: true}},
- {name: "*", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "foo/*/*", opts: refname.Options{RefspecPattern: true}},
- {name: "foo/*/*", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "*/foo/*", opts: refname.Options{RefspecPattern: true}},
- {name: "*/foo/*", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "*/*/foo", opts: refname.Options{RefspecPattern: true}},
- {name: "*/*/foo", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "/foo"},
- {name: "/foo", opts: refname.Options{AllowOneLevel: true}},
- {name: "/foo", opts: refname.Options{RefspecPattern: true}},
- {name: "/foo", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- {name: "@"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name+"_"+tt.opts.String(), func(t *testing.T) {
- t.Parallel()
-
- err := refname.Validate(tt.name, tt.opts)
- gitErr := gitCheckRefFormat(t, tt.name, tt.opts)
-
- if (err == nil) != (gitErr == nil) {
- t.Fatalf("ValidateName(%q, %+v) err=%v, git err=%v", tt.name, tt.opts, err, gitErr)
- }
- })
- }
-}
-
-func TestNormalizeNameAgainstGit(t *testing.T) {
- t.Parallel()
-
- type testCase struct {
- name string
- opts refname.Options
- }
-
- tests := []testCase{
- {name: "/"},
- {name: "/", opts: refname.Options{AllowOneLevel: true}},
- {name: "///refs///heads//main"},
- {name: "refs////tags///v1"},
- {name: "refs///heads///"},
- {name: "HEAD", opts: refname.Options{AllowOneLevel: true}},
- {name: "refs/heads/*", opts: refname.Options{RefspecPattern: true}},
- {name: "refs///heads/foo"},
- {name: "/heads/foo", opts: refname.Options{AllowOneLevel: true}},
- {name: "///heads/foo"},
- {name: "heads/foo/../bar"},
- {name: "heads/./foo"},
- {name: "heads\\foo"},
- {name: "heads/foo.lock"},
- {name: "heads///foo.lock"},
- {name: "foo.lock/bar"},
- {name: "foo.lock///bar"},
- {name: "foo"},
- {name: "/foo", opts: refname.Options{AllowOneLevel: true}},
- {name: "/foo", opts: refname.Options{AllowOneLevel: true, RefspecPattern: true}},
- }
-
- for _, tt := range tests {
- t.Run(tt.name+"_"+tt.opts.String(), func(t *testing.T) {
- t.Parallel()
-
- got, err := refname.Normalize(tt.name, tt.opts)
- want, gitErr := gitNormalizeRefFormat(t, tt.name, tt.opts)
-
- if (err == nil) != (gitErr == nil) {
- t.Fatalf("NormalizeName(%q, %+v) err=%v, git err=%v", tt.name, tt.opts, err, gitErr)
- }
-
- if err == nil && got != want {
- t.Fatalf("NormalizeName(%q, %+v) = %q, want %q", tt.name, tt.opts, got, want)
- }
- })
- }
-}
-
-func TestBranchNameAgainstGit(t *testing.T) {
- t.Parallel()
-
- tests := []string{
- "main",
- "feature/topic",
- "-main",
- "HEAD",
- "@{-1}",
- "feature.lock",
- "topic@{1}",
- "refs/heads/main",
- "refs/heads/HEAD",
- "refs/tags/x",
- }
-
- for _, name := range tests {
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- got, err := refname.Branch(name)
- want, gitErr := gitCheckBranchName(t, name)
-
- if (err == nil) != (gitErr == nil) {
- t.Fatalf("BranchName(%q) err=%v, git err=%v", name, err, gitErr)
- }
-
- if err == nil && got != want {
- t.Fatalf("BranchName(%q) = %q, want %q", name, got, want)
- }
- })
- }
-}
-
-func TestTagName(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- }{
- {name: "v1.0.0"},
- {name: "main/topic"},
- {name: "-bad"},
- {name: "HEAD"},
- {name: "feature.lock"},
- {name: "refs/tags/v1.0.0"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- got, err := refname.Tag(tt.name)
- want, gitErr := gitCheckTagName(t, tt.name)
-
- if (err == nil) != (gitErr == nil) {
- t.Fatalf("TagName(%q) err=%v, git err=%v", tt.name, err, gitErr)
- }
-
- if err == nil && got != want {
- t.Fatalf("TagName(%q) = %q, want %q", tt.name, got, want)
- }
- })
- }
-}
-
-func TestIsSafeName(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want bool
- }{
- {name: "", want: false},
- {name: "HEAD", want: true},
- {name: "MERGE_HEAD", want: true},
- {name: "Head", want: false},
- {name: "refs/heads/main", want: true},
- {name: "refs/", want: false},
- {name: "refs//heads/main", want: false},
- {name: "refs/heads/main/", want: false},
- {name: "refs/foo/../bar", want: false},
- {name: "refs/foo/../../bar", want: false},
- {name: "refs/heads/main.lock", want: true},
- }
-
- for _, tt := range tests {
- if got := refname.IsSafe(tt.name); got != tt.want {
- t.Fatalf("IsSafeName(%q) = %v, want %v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestIsPerWorktree(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want bool
- }{
- {name: "refs/worktree/foo", want: true},
- {name: "refs/bisect/foo", want: true},
- {name: "refs/rewritten/foo", want: true},
- {name: "refs/heads/foo", want: false},
- {name: "worktrees/wt1/HEAD", want: false},
- }
-
- for _, tt := range tests {
- if got := refname.IsPerWorktree(tt.name); got != tt.want {
- t.Fatalf("IsPerWorktree(%q) = %v, want %v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestIsPseudo(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want bool
- }{
- {name: "FETCH_HEAD", want: true},
- {name: "MERGE_HEAD", want: true},
- {name: "HEAD", want: false},
- {name: "AUTO_MERGE", want: false},
- }
-
- for _, tt := range tests {
- if got := refname.IsPseudo(tt.name); got != tt.want {
- t.Fatalf("IsPseudo(%q) = %v, want %v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestIsRootSyntax(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want bool
- }{
- {name: "", want: true},
- {name: "HEAD", want: true},
- {name: "AUTO_MERGE", want: true},
- {name: "BISECT-EXPECTED_REV", want: true},
- {name: "refs/heads/main", want: false},
- {name: "Head", want: false},
- {name: "HEAD1", want: false},
- }
-
- for _, tt := range tests {
- if got := refname.IsRootSyntax(tt.name); got != tt.want {
- t.Fatalf("IsRootSyntax(%q) = %v, want %v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestIsRoot(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want bool
- }{
- {name: "HEAD", want: true},
- {name: "ORIG_HEAD", want: true},
- {name: "BOGUS_HEAD", want: true},
- {name: "CHERRY_PICK_HEAD", want: true},
- {name: "REVERT_HEAD", want: true},
- {name: "AUTO_MERGE", want: true},
- {name: "BISECT_EXPECTED_REV", want: true},
- {name: "NOTES_MERGE_PARTIAL", want: true},
- {name: "NOTES_MERGE_REF", want: true},
- {name: "MERGE_AUTOSTASH", want: true},
- {name: "FETCH_HEAD", want: false},
- {name: "MERGE_HEAD", want: false},
- {name: "Head", want: false},
- {name: "refs/heads/main", want: false},
- }
-
- for _, tt := range tests {
- if got := refname.IsRoot(tt.name); got != tt.want {
- t.Fatalf("IsRoot(%q) = %v, want %v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestParseWorktree(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- want refname.ParsedWorktreeRef
- }{
- {
- name: "refs/heads/main",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeShared,
- BareRefName: "refs/heads/main",
- },
- },
- {
- name: "HEAD",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeCurrent,
- BareRefName: "HEAD",
- },
- },
- {
- name: "refs/worktree/foo",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeCurrent,
- BareRefName: "refs/worktree/foo",
- },
- },
- {
- name: "main-worktree/HEAD",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeMain,
- BareRefName: "HEAD",
- },
- },
- {
- name: "main-worktree/FOO",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeMain,
- BareRefName: "FOO",
- },
- },
- {
- name: "main-worktree/refs/worktree/foo",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeMain,
- BareRefName: "refs/worktree/foo",
- },
- },
- {
- name: "main-worktree/",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeMain,
- BareRefName: "",
- },
- },
- {
- name: "main-worktree/refs/heads/main",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeShared,
- BareRefName: "main-worktree/refs/heads/main",
- },
- },
- {
- name: "worktrees/wt1/HEAD",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeOther,
- WorktreeName: "wt1",
- BareRefName: "HEAD",
- },
- },
- {
- name: "worktrees/wt1/BAR",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeOther,
- WorktreeName: "wt1",
- BareRefName: "BAR",
- },
- },
- {
- name: "worktrees/wt1/refs/bisect/foo",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeOther,
- WorktreeName: "wt1",
- BareRefName: "refs/bisect/foo",
- },
- },
- {
- name: "worktrees/wt1/refs/heads/main",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeShared,
- BareRefName: "worktrees/wt1/refs/heads/main",
- },
- },
- {
- name: "worktrees/wt1",
- want: refname.ParsedWorktreeRef{
- Type: refname.WorktreeOther,
- WorktreeName: "wt1",
- BareRefName: "",
- },
- },
- }
-
- for _, tt := range tests {
- if got := refname.ParseWorktree(tt.name); got != tt.want {
- t.Fatalf("ParseWorktree(%q) = %#v, want %#v", tt.name, got, tt.want)
- }
- }
-}
-
-func TestValidateUpdateName(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- hasNewValue bool
- wantErr bool
- }{
- {name: "refs/heads/main", hasNewValue: true, wantErr: false},
- {name: "HEAD", hasNewValue: true, wantErr: false},
- {name: "PSEUDOREF", hasNewValue: true, wantErr: false},
- {name: "FETCH_HEAD", hasNewValue: true, wantErr: true},
- {name: "MERGE_HEAD", hasNewValue: true, wantErr: true},
- {name: "refs/heads/.bad", hasNewValue: true, wantErr: true},
- {name: "foo/bar", hasNewValue: true, wantErr: false},
- {name: "foo/bar", hasNewValue: false, wantErr: true},
- {name: "PSEUDOREF", hasNewValue: false, wantErr: false},
- {name: "HEAD", hasNewValue: false, wantErr: false},
- }
-
- for _, tt := range tests {
- err := refname.ValidateUpdateName(tt.name, tt.hasNewValue)
- if (err != nil) != tt.wantErr {
- t.Fatalf("ValidateUpdateName(%q, %v) err=%v, wantErr=%v", tt.name, tt.hasNewValue, err, tt.wantErr)
- }
- }
-}
-
-func TestValidateSymbolicTarget(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- ref string
- target string
- wantErr bool
- }{
- {ref: "HEAD", target: "refs/heads/main", wantErr: false},
- {ref: "HEAD", target: "foo", wantErr: true},
- {ref: "HEAD", target: "ORIG_HEAD", wantErr: true},
- {ref: "refs/heads/top", target: "ORIG_HEAD", wantErr: false},
- {ref: "refs/heads/top", target: "refs/heads/main", wantErr: false},
- {ref: "refs/heads/top", target: "worktrees/wt1/HEAD", wantErr: false},
- {ref: "refs/heads/top", target: "foo", wantErr: true},
- {ref: "refs/heads/top", target: "foo..bar", wantErr: true},
- {ref: "main-worktree/HEAD", target: "refs/heads/main", wantErr: false},
- {ref: "main-worktree/HEAD", target: "refs/tags/v1", wantErr: true},
- }
-
- for _, tt := range tests {
- err := refname.ValidateSymbolicTarget(tt.ref, tt.target)
- if (err != nil) != tt.wantErr {
- t.Fatalf("ValidateSymbolicTarget(%q, %q) err=%v, wantErr=%v", tt.ref, tt.target, err, tt.wantErr)
- }
- }
-}
-
-func TestSanitizeComponent(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- component string
- want string
- }{
- {component: ".", want: "-"},
- {component: "..", want: "-"},
- {component: "foo..bar", want: "foo.bar"},
- {component: "foo.lock", want: "foo"},
- {component: "foo.lock.lock", want: "foo"},
- {component: "foo bar", want: "foo-bar"},
- {component: "@", want: "-/@"},
- {component: "a@{b", want: "a@-b"},
- {component: "a*b", want: "a-b"},
- }
-
- for _, tt := range tests {
- if got := refname.SanitizeComponent(tt.component); got != tt.want {
- t.Fatalf("SanitizeComponent(%q) = %q, want %q", tt.component, got, tt.want)
- }
- }
-}
-
-func gitCheckRefFormat(tb testing.TB, name string, opts refname.Options) error {
- tb.Helper()
-
- args := []string{"check-ref-format"}
- if opts.AllowOneLevel {
- args = append(args, "--allow-onelevel")
- }
-
- if opts.RefspecPattern {
- args = append(args, "--refspec-pattern")
- }
-
- args = append(args, name)
-
- return exec.CommandContext(context.Background(), "git", args...).Run()
-}
-
-func gitNormalizeRefFormat(tb testing.TB, name string, opts refname.Options) (string, error) {
- tb.Helper()
-
- args := []string{"check-ref-format", "--normalize"}
- if opts.AllowOneLevel {
- args = append(args, "--allow-onelevel")
- }
-
- if opts.RefspecPattern {
- args = append(args, "--refspec-pattern")
- }
-
- args = append(args, name)
-
- out, err := exec.CommandContext(context.Background(), "git", args...).Output()
- if err != nil {
- return "", err
- }
-
- return strings.TrimSuffix(string(out), "\n"), nil
-}
-
-func gitCheckBranchName(tb testing.TB, name string) (string, error) {
- tb.Helper()
-
- cmd := exec.CommandContext(context.Background(), "git", "check-ref-format", "--branch", name)
- cmd.Dir = tb.TempDir()
-
- out, err := cmd.Output()
- if err != nil {
- return "", err
- }
-
- branchName := strings.TrimSuffix(string(out), "\n")
- if strings.HasPrefix(branchName, "refs/") {
- return branchName, nil
- }
-
- return "refs/heads/" + branchName, nil
-}
-
-func gitCheckTagName(tb testing.TB, name string) (string, error) {
- tb.Helper()
-
- if strings.HasPrefix(name, "-") || name == "HEAD" {
- return "", exec.ErrNotFound
- }
-
- //nolint:gosec
- err := exec.CommandContext(
- context.Background(),
- "git",
- "check-ref-format",
- "refs/tags/"+name,
- ).Run()
- if err != nil {
- return "", err
- }
-
- return "refs/tags/" + name, nil
-}
diff --git a/ref/name/root.go b/ref/name/root.go
deleted file mode 100644
index 43361846..00000000
--- a/ref/name/root.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package refname
-
-import "strings"
-
-// IsRoot reports whether name is one root ref according to Git.
-func IsRoot(name string) bool {
- if !IsRootSyntax(name) || IsPseudo(name) {
- return false
- }
-
- if strings.HasSuffix(name, "_HEAD") {
- return true
- }
-
- switch name {
- case "HEAD", "AUTO_MERGE", "BISECT_EXPECTED_REV", "NOTES_MERGE_PARTIAL", "NOTES_MERGE_REF", "MERGE_AUTOSTASH":
- return true
- default:
- return false
- }
-}
diff --git a/ref/name/root_syntax.go b/ref/name/root_syntax.go
deleted file mode 100644
index 97a15cb9..00000000
--- a/ref/name/root_syntax.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package refname
-
-// IsRootSyntax reports whether name matches Git's all-caps root-ref syntax.
-func IsRootSyntax(name string) bool {
- for i := range len(name) {
- ch := name[i]
- if (ch < 'A' || ch > 'Z') && ch != '-' && ch != '_' {
- return false
- }
- }
-
- return true
-}
diff --git a/ref/name/safe.go b/ref/name/safe.go
deleted file mode 100644
index b36d3b2f..00000000
--- a/ref/name/safe.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package refname
-
-import "strings"
-
-// IsSafe reports whether name is one safe refname for direct filesystem
-// operations; see refname_is_safe.
-func IsSafe(name string) bool {
- rest, ok := strings.CutPrefix(name, "refs/")
- if ok {
- if rest == "" || rest[0] == '/' || rest[len(rest)-1] == '/' {
- return false
- }
-
- normalized, normOK := normalizeRefPath(rest)
-
- return normOK && normalized == rest
- }
-
- if name == "" {
- return false
- }
-
- for i := range len(name) {
- ch := name[i]
- if (ch < 'A' || ch > 'Z') && ch != '_' {
- return false
- }
- }
-
- return true
-}
diff --git a/ref/name/sanitize.go b/ref/name/sanitize.go
deleted file mode 100644
index f543de7c..00000000
--- a/ref/name/sanitize.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package refname
-
-import (
- "fmt"
- "strings"
-)
-
-// SanitizeComponent mutates component until it satisfies
-// sanitize_refname_component.
-func SanitizeComponent(component string) string {
- var builder strings.Builder
-
- err := checkOrSanitizeRefname(component, refnameAllowOneLevel, &builder)
- if err != nil {
- panic(fmt.Sprintf("ref: sanitize component %q: %v", component, err))
- }
-
- return builder.String()
-}
diff --git a/ref/name/slashes.go b/ref/name/slashes.go
deleted file mode 100644
index 44d3e4ea..00000000
--- a/ref/name/slashes.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package refname
-
-import "strings"
-
-func collapseSlashes(name string) string {
- if name == "" {
- return ""
- }
-
- var builder strings.Builder
- builder.Grow(len(name))
-
- prev := byte('/')
-
- for i := range len(name) {
- ch := name[i]
- if prev == '/' && ch == '/' {
- continue
- }
-
- builder.WriteByte(ch)
- prev = ch
- }
-
- return builder.String()
-}
diff --git a/ref/name/tag.go b/ref/name/tag.go
deleted file mode 100644
index 226c0fdd..00000000
--- a/ref/name/tag.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package refname
-
-import "strings"
-
-// Tag checks one tag shorthand and returns its fully-qualified
-// refs/tags/... name.
-func Tag(name string) (string, error) {
- if strings.HasPrefix(name, "-") || name == "HEAD" {
- return "", &NameError{Name: name, Reason: "invalid tag name"}
- }
-
- full := "refs/tags/" + name
-
- err := validate(full, 0)
- if err != nil {
- return "", err
- }
-
- return full, nil
-}
diff --git a/ref/name/update.go b/ref/name/update.go
deleted file mode 100644
index 92830f1a..00000000
--- a/ref/name/update.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package refname
-
-import "strings"
-
-// ValidateUpdateName checks whether name is valid for one direct ref update.
-//
-// See transaction_refname_valid();
-// updates with a new OID use check_refname_format(..., ALLOW_ONELEVEL),
-// while delete/verify style operations use refname_is_safe().
-func ValidateUpdateName(name string, hasNewValue bool) error {
- if IsPseudo(name) {
- return &NameError{Name: name, Reason: "pseudoref updates are not allowed"}
- }
-
- if hasNewValue {
- return Validate(name, Options{AllowOneLevel: true})
- }
-
- if !IsSafe(name) {
- return &NameError{Name: name, Reason: "unsafe refname for update"}
- }
-
- return nil
-}
-
-// ValidateSymbolicTarget checks whether target is valid for one symref target.
-//
-// See refs_fsck_symref();
-// root refs are allowed directly, HEAD must point to refs/heads/...,
-// and non-root targets must be valid full refnames rooted at refs/ or
-// worktrees/.
-func ValidateSymbolicTarget(refname string, target string) error {
- parsed := ParseWorktree(refname)
- if parsed.BareRefName == "HEAD" && !strings.HasPrefix(target, "refs/heads/") {
- return &NameError{Name: target, Reason: refname + " must point to refs/heads/..."}
- }
-
- if IsRoot(target) {
- return nil
- }
-
- err := Validate(target, Options{})
- if err != nil {
- return err
- }
-
- if strings.HasPrefix(target, "refs/") {
- return nil
- }
-
- if strings.HasPrefix(target, "worktrees/") {
- return nil
- }
-
- return &NameError{Name: target, Reason: "symref target is not a ref"}
-}
diff --git a/ref/name/utils.go b/ref/name/utils.go
deleted file mode 100644
index 58944748..00000000
--- a/ref/name/utils.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package refname
-
-import (
- "strings"
-)
-
-func overwriteLastByte(builder *strings.Builder, ch byte) {
- overwriteBuilderAt(builder, builder.Len()-1, ch)
-}
-
-func overwriteBuilderAt(builder *strings.Builder, index int, ch byte) {
- value := builder.String()
- truncateBuilder(builder, index)
- builder.WriteByte(ch)
- builder.WriteString(value[index+1:])
-}
-
-func truncateBuilder(builder *strings.Builder, n int) {
- value := builder.String()
- builder.Reset()
- builder.WriteString(value[:n])
-}
diff --git a/ref/name/validate.go b/ref/name/validate.go
deleted file mode 100644
index 1b8ad396..00000000
--- a/ref/name/validate.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package refname
-
-import "strings"
-
-// Validate checks whether name is one valid Git refname.
-func Validate(name string, options Options) error {
- return validate(name, options.flags())
-}
-
-func validate(name string, flags int) error {
- return checkOrSanitizeRefname(name, flags, nil)
-}
-
-func checkOrSanitizeRefname(name string, flags int, sanitized *strings.Builder) error {
- componentCount := 0
- remaining := name
-
- if name == "@" {
- if sanitized == nil {
- return &NameError{Name: name, Reason: "single @ is not allowed"}
- }
-
- sanitized.WriteByte('-')
- }
-
- for {
- if sanitized != nil && sanitized.Len() > 0 {
- sanitized.WriteByte('/')
- }
-
- componentLen, err := checkRefnameComponent(remaining, &flags, sanitized, name)
- switch {
- case sanitized != nil && componentLen == 0:
- case componentLen <= 0:
- if err != nil {
- return err
- }
-
- return &NameError{Name: name, Reason: "component has zero length"}
- case err != nil:
- return err
- }
-
- componentCount++
-
- if componentLen == len(remaining) {
- break
- }
-
- remaining = remaining[componentLen+1:]
- }
-
- componentLen := len(remaining)
- if componentLen > 0 && remaining[componentLen-1] == '.' {
- if sanitized == nil {
- return &NameError{Name: name, Reason: "name ends with '.'"}
- }
- }
-
- if flags&refnameAllowOneLevel == 0 && componentCount < 2 {
- return &NameError{Name: name, Reason: "one-level refname is not allowed"}
- }
-
- return nil
-}
diff --git a/ref/name/worktree.go b/ref/name/worktree.go
deleted file mode 100644
index 48ca215d..00000000
--- a/ref/name/worktree.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package refname
-
-import "strings"
-
-// WorktreeType classifies one worktree-qualified refname prefix.
-type WorktreeType uint8
-
-const (
- // WorktreeShared is one ordinary shared refname.
- WorktreeShared WorktreeType = iota
-
- // WorktreeCurrent is one current-worktree-only refname like HEAD or refs/worktree/...
- WorktreeCurrent
-
- // WorktreeMain is one main-worktree-qualified refname like main-worktree/HEAD.
- WorktreeMain
-
- // WorktreeOther is one other-worktree-qualified refname like worktrees/wt1/HEAD.
- WorktreeOther
-)
-
-// IsPerWorktree reports whether name is one per-worktree ref namespace.
-func IsPerWorktree(name string) bool {
- return strings.HasPrefix(name, "refs/worktree/") ||
- strings.HasPrefix(name, "refs/bisect/") ||
- strings.HasPrefix(name, "refs/rewritten/")
-}
-
-// ParsedWorktreeRef is the result of parsing one worktree-qualified refname.
-type ParsedWorktreeRef struct {
- Type WorktreeType
- WorktreeName string
- BareRefName string
-}
-
-// ParseWorktree parses Git's worktree ref prefixes.
-func ParseWorktree(name string) ParsedWorktreeRef {
- if bare, ok := strings.CutPrefix(name, "worktrees/"); ok {
- worktreeName, rest, found := strings.Cut(bare, "/")
- if !found {
- return ParsedWorktreeRef{
- Type: WorktreeOther,
- WorktreeName: worktreeName,
- BareRefName: "",
- }
- }
-
- if isCurrentWorktreeRef(rest) {
- return ParsedWorktreeRef{
- Type: WorktreeOther,
- WorktreeName: worktreeName,
- BareRefName: rest,
- }
- }
- }
-
- if bare, ok := strings.CutPrefix(name, "main-worktree/"); ok && isCurrentWorktreeRef(bare) {
- return ParsedWorktreeRef{
- Type: WorktreeMain,
- BareRefName: bare,
- }
- }
-
- if isCurrentWorktreeRef(name) {
- return ParsedWorktreeRef{
- Type: WorktreeCurrent,
- BareRefName: name,
- }
- }
-
- return ParsedWorktreeRef{
- Type: WorktreeShared,
- BareRefName: name,
- }
-}
diff --git a/ref/ref.go b/ref/ref.go
deleted file mode 100644
index 0c70cc26..00000000
--- a/ref/ref.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package ref
-
-// Ref is a Git reference.
-//
-// Consider casting to [Detached] or [Symbolic].
-type Ref interface {
- isRef()
- Name() string
-}
diff --git a/ref/store/batch.go b/ref/store/batch.go
deleted file mode 100644
index 11423cec..00000000
--- a/ref/store/batch.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package refstore
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Batch stages reference operations for one non-atomic apply.
-//
-// Unlike Transaction, Batch may reject some queued operations while still
-// applying others successfully when Apply runs.
-//
-// A batch borrows its underlying store and is invalid after that store is
-// closed.
-//
-// Labels: MT-Unsafe.
-type Batch interface {
- // Create creates one detached reference, requiring that the logical
- // reference does not already exist.
- Create(name string, newID objectid.ObjectID) error
- // Update updates one detached reference, requiring that the current logical
- // reference value matches oldID.
- Update(name string, newID, oldID objectid.ObjectID) error
- // Delete deletes one detached reference, requiring that the current logical
- // reference value matches oldID.
- Delete(name string, oldID objectid.ObjectID) error
- // Verify verifies that the current logical reference value matches oldID.
- Verify(name string, oldID objectid.ObjectID) error
-
- // CreateSymbolic creates one symbolic reference, requiring that the named
- // reference does not already exist.
- CreateSymbolic(name, newTarget string) error
- // UpdateSymbolic updates one symbolic reference directly, requiring that its
- // current target matches oldTarget.
- UpdateSymbolic(name, newTarget, oldTarget string) error
- // DeleteSymbolic deletes one symbolic reference directly, requiring that its
- // current target matches oldTarget.
- DeleteSymbolic(name, oldTarget string) error
- // VerifySymbolic verifies that the named symbolic reference currently points
- // at oldTarget.
- VerifySymbolic(name, oldTarget string) error
-
- // Apply validates and applies queued operations, returning one result per
- // queued operation in order. Fatal backend failures are returned separately.
- //
- // Malformed operations are rejected by the queueing methods above and do not
- // enter the batch.
- //
- // Apply invalidates the receiver.
- Apply() ([]BatchResult, error)
- // Abort abandons the batch and releases any resources it holds.
- //
- // Abort invalidates the receiver.
- Abort() error
-}
-
-// BatchStatus reports the outcome for one queued batch operation.
-type BatchStatus uint8
-
-const (
- BatchStatusApplied BatchStatus = iota
- BatchStatusRejected
- BatchStatusFatal
- BatchStatusNotAttempted
-)
-
-// BatchResult reports the outcome for one queued batch operation.
-type BatchResult struct {
- Name string
- Status BatchStatus
- Error error
-}
diff --git a/ref/store/batch_store.go b/ref/store/batch_store.go
deleted file mode 100644
index 725a05e5..00000000
--- a/ref/store/batch_store.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package refstore
-
-// Batcher begins non-atomic reference batches.
-type Batcher interface {
- // BeginBatch creates one new queued batch.
- //
- // Labels: Life-Parent.
- BeginBatch() (Batch, error)
-}
diff --git a/ref/store/chain/chain.go b/ref/store/chain/chain.go
deleted file mode 100644
index a332f64c..00000000
--- a/ref/store/chain/chain.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Package chain provides a wrapper reference storage backend to query a chain
-// of backends.
-package chain
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// Chain queries multiple reference stores in order.
-//
-// Labels: Close-Caller.
-type Chain struct {
- backends []refstore.Reader
-}
diff --git a/ref/store/chain/close.go b/ref/store/chain/close.go
deleted file mode 100644
index 75fa357e..00000000
--- a/ref/store/chain/close.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package chain
-
-// Close releases wrapper-local resources.
-//
-// Labels: MT-Unsafe.
-func (chain *Chain) Close() error { return nil }
diff --git a/ref/store/chain/list.go b/ref/store/chain/list.go
deleted file mode 100644
index c577ca85..00000000
--- a/ref/store/chain/list.go
+++ /dev/null
@@ -1,40 +0,0 @@
-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 {
- 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/ref/store/chain/new.go b/ref/store/chain/new.go
deleted file mode 100644
index 1c8a3a28..00000000
--- a/ref/store/chain/new.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package chain
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// New creates an ordered reference store chain.
-//
-// The provided backends must be non-nil and distinct.
-//
-// Labels: Deps-Borrowed, Life-Parent.
-func New(backends ...refstore.Reader) *Chain {
- return &Chain{
- backends: append([]refstore.Reader(nil), backends...),
- }
-}
diff --git a/ref/store/chain/resolve.go b/ref/store/chain/resolve.go
deleted file mode 100644
index 06c3d8f5..00000000
--- a/ref/store/chain/resolve.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package chain
-
-import (
- "errors"
- "fmt"
-
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// 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 {
- 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
-}
-
-// ResolveToDetached resolves symbolic references through Resolve until detached.
-//
-// It intentionally does not call backend ResolveToDetached. This allows symbolic
-// references to cross backends in the chain.
-func (chain *Chain) ResolveToDetached(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/ref/store/doc.go b/ref/store/doc.go
deleted file mode 100644
index 8f7f39a6..00000000
--- a/ref/store/doc.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Package refstore provides interfaces for reference storage backends.
-//
-// Ref stores directly use reference values. Unlike object storage, they
-// do not have a separate fetch layer to parse backend results into
-// higher-level forms.
-//
-// The package separates read-only access from atomic transactions and
-// non-atomic batches. Not every readable ref backend is writable, and not
-// every writable backend necessarily offers the same update model.
-//
-// Concrete implementations generally inherit the contract documented by the
-// interfaces they satisfy. Implementation docs focus on additional guarantees
-// and implementation-specific behavior.
-package refstore
diff --git a/ref/store/errors.go b/ref/store/errors.go
deleted file mode 100644
index 45583440..00000000
--- a/ref/store/errors.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package refstore
-
-import "errors"
-
-// ErrReferenceNotFound indicates that a reference does not exist in a backend.
-// TODO: Interface error? Just like object not found in objectstore.
-var ErrReferenceNotFound = errors.New("refstore: reference not found")
diff --git a/ref/store/files/batch.go b/ref/store/files/batch.go
deleted file mode 100644
index d8037bbb..00000000
--- a/ref/store/files/batch.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package files
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// Batch stages files-store updates for one non-atomic apply.
-type Batch struct {
- store *Store
- ops []queuedUpdate
-}
-
-var _ refstore.Batch = (*Batch)(nil)
diff --git a/ref/store/files/batch_abort.go b/ref/store/files/batch_abort.go
deleted file mode 100644
index c229ca06..00000000
--- a/ref/store/files/batch_abort.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package files
-
-// Abort abandons the queued updates.
-func (batch *Batch) Abort() error {
- return nil
-}
diff --git a/ref/store/files/batch_apply.go b/ref/store/files/batch_apply.go
deleted file mode 100644
index 1847b544..00000000
--- a/ref/store/files/batch_apply.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package files
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// Apply validates and applies the queued updates.
-func (batch *Batch) Apply() ([]refstore.BatchResult, error) {
- results := make([]refstore.BatchResult, len(batch.ops))
- remainingIdx := make([]int, 0, len(batch.ops))
- remainingOps := make([]queuedUpdate, 0, len(batch.ops))
- seenTargets := make(map[string]struct{}, len(batch.ops))
- executor := &refUpdateExecutor{store: batch.store}
-
- for i, op := range batch.ops {
- results[i].Name = op.name
-
- target, err := executor.resolveQueuedUpdateTarget(op)
- if err != nil {
- if isBatchRejected(err) {
- results[i].Status = refstore.BatchStatusRejected
- results[i].Error = batchResultError(err)
-
- continue
- }
-
- results[i].Status = refstore.BatchStatusFatal
- results[i].Error = batchResultError(err)
-
- for j := i + 1; j < len(results); j++ {
- results[j].Name = batch.ops[j].name
- results[j].Status = refstore.BatchStatusNotAttempted
- results[j].Error = batchResultError(err)
- }
-
- return results, err
- }
-
- targetKey := updateTargetKey(target.loc)
- if _, exists := seenTargets[targetKey]; exists {
- results[i].Status = refstore.BatchStatusRejected
- results[i].Error = &refstore.DuplicateUpdateError{}
-
- continue
- }
-
- seenTargets[targetKey] = struct{}{}
-
- remainingIdx = append(remainingIdx, i)
- remainingOps = append(remainingOps, op)
- }
-
- for len(remainingOps) > 0 {
- prepared, err := executor.prepareUpdates(remainingOps)
- if err == nil {
- err = executor.commitPreparedUpdates(prepared)
- if err == nil {
- for _, idx := range remainingIdx {
- results[idx].Status = refstore.BatchStatusApplied
- }
-
- return results, nil
- }
-
- fatalName := batchResultName(err)
-
- fatalMarked := false
- for i, idx := range remainingIdx {
- if !fatalMarked && remainingOps[i].name == fatalName && fatalName != "" {
- results[idx].Status = refstore.BatchStatusFatal
- results[idx].Error = batchResultError(err)
- fatalMarked = true
-
- continue
- }
-
- results[idx].Status = refstore.BatchStatusNotAttempted
- results[idx].Error = batchResultError(err)
- }
-
- return results, err
- }
-
- if !isBatchRejected(err) {
- fatalName := batchResultName(err)
-
- fatalMarked := false
- for i, idx := range remainingIdx {
- if !fatalMarked && remainingOps[i].name == fatalName && fatalName != "" {
- results[idx].Status = refstore.BatchStatusFatal
- results[idx].Error = batchResultError(err)
- fatalMarked = true
-
- continue
- }
-
- results[idx].Status = refstore.BatchStatusNotAttempted
- results[idx].Error = batchResultError(err)
- }
-
- return results, err
- }
-
- name := batchResultName(err)
- rejectedAt := -1
-
- for i, op := range remainingOps {
- if op.name == name {
- rejectedAt = i
-
- break
- }
- }
-
- if rejectedAt < 0 {
- for _, idx := range remainingIdx {
- results[idx].Status = refstore.BatchStatusNotAttempted
- results[idx].Error = batchResultError(err)
- }
-
- return results, err
- }
-
- results[remainingIdx[rejectedAt]].Status = refstore.BatchStatusRejected
- results[remainingIdx[rejectedAt]].Error = batchResultError(err)
- remainingIdx = append(remainingIdx[:rejectedAt], remainingIdx[rejectedAt+1:]...)
- remainingOps = append(remainingOps[:rejectedAt], remainingOps[rejectedAt+1:]...)
- }
-
- return results, nil
-}
diff --git a/ref/store/files/batch_begin.go b/ref/store/files/batch_begin.go
deleted file mode 100644
index c8a0dca7..00000000
--- a/ref/store/files/batch_begin.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package files
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// BeginBatch creates one new files batch.
-//
-//nolint:ireturn
-func (store *Store) BeginBatch() (refstore.Batch, error) {
- return &Batch{
- store: store,
- ops: make([]queuedUpdate, 0, 8),
- }, nil
-}
diff --git a/ref/store/files/batch_queue.go b/ref/store/files/batch_queue.go
deleted file mode 100644
index afec5a78..00000000
--- a/ref/store/files/batch_queue.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package files
-
-func (batch *Batch) queue(op queuedUpdate) error {
- err := (&refUpdateExecutor{store: batch.store}).validateQueuedUpdate(op)
- if err != nil {
- return err
- }
-
- batch.ops = append(batch.ops, op)
-
- return nil
-}
diff --git a/ref/store/files/batch_queue_ops.go b/ref/store/files/batch_queue_ops.go
deleted file mode 100644
index 441bcaba..00000000
--- a/ref/store/files/batch_queue_ops.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package files
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Create queues a detached reference creation.
-func (batch *Batch) Create(name string, newID objectid.ObjectID) error {
- return batch.queue(queuedUpdate{name: name, kind: updateCreate, newID: newID})
-}
-
-// Update queues a detached reference update.
-func (batch *Batch) Update(name string, newID, oldID objectid.ObjectID) error {
- return batch.queue(queuedUpdate{name: name, kind: updateReplace, newID: newID, oldID: oldID})
-}
-
-// Delete queues a detached reference deletion.
-func (batch *Batch) Delete(name string, oldID objectid.ObjectID) error {
- return batch.queue(queuedUpdate{name: name, kind: updateDelete, oldID: oldID})
-}
-
-// Verify queues a detached reference verification.
-func (batch *Batch) Verify(name string, oldID objectid.ObjectID) error {
- return batch.queue(queuedUpdate{name: name, kind: updateVerify, oldID: oldID})
-}
-
-// CreateSymbolic queues a symbolic reference creation.
-func (batch *Batch) CreateSymbolic(name, newTarget string) error {
- return batch.queue(queuedUpdate{name: name, kind: updateCreateSymbolic, newTarget: newTarget})
-}
-
-// UpdateSymbolic queues a symbolic reference update.
-func (batch *Batch) UpdateSymbolic(name, newTarget, oldTarget string) error {
- return batch.queue(queuedUpdate{name: name, kind: updateReplaceSymbolic, newTarget: newTarget, oldTarget: oldTarget})
-}
-
-// DeleteSymbolic queues a symbolic reference deletion.
-func (batch *Batch) DeleteSymbolic(name, oldTarget string) error {
- return batch.queue(queuedUpdate{name: name, kind: updateDeleteSymbolic, oldTarget: oldTarget})
-}
-
-// VerifySymbolic queues a symbolic reference verification.
-func (batch *Batch) VerifySymbolic(name, oldTarget string) error {
- return batch.queue(queuedUpdate{name: name, kind: updateVerifySymbolic, oldTarget: oldTarget})
-}
diff --git a/ref/store/files/batch_rejection.go b/ref/store/files/batch_rejection.go
deleted file mode 100644
index 7f31536c..00000000
--- a/ref/store/files/batch_rejection.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package files
-
-import (
- "errors"
-
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func isBatchRejected(err error) bool {
- _, invalidName := errors.AsType[*refstore.InvalidNameError](err)
- _, invalidValue := errors.AsType[*refstore.InvalidValueError](err)
- _, duplicateUpdate := errors.AsType[*refstore.DuplicateUpdateError](err)
- _, createExists := errors.AsType[*refstore.CreateExistsError](err)
- _, incorrectOldValue := errors.AsType[*refstore.IncorrectOldValueError](err)
- _, expectedDetached := errors.AsType[*refstore.ExpectedDetachedError](err)
- _, expectedSymbolic := errors.AsType[*refstore.ExpectedSymbolicError](err)
- _, nameConflict := errors.AsType[*refstore.NameConflictError](err)
-
- return errors.Is(err, refstore.ErrReferenceNotFound) ||
- invalidName ||
- invalidValue ||
- duplicateUpdate ||
- createExists ||
- incorrectOldValue ||
- expectedDetached ||
- expectedSymbolic ||
- nameConflict
-}
diff --git a/ref/store/files/batch_result_error.go b/ref/store/files/batch_result_error.go
deleted file mode 100644
index 06d68273..00000000
--- a/ref/store/files/batch_result_error.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package files
-
-import "errors"
-
-func batchResultError(err error) error {
- updateErr, ok := errors.AsType[*updateContextError](err)
- if ok {
- return updateErr.err
- }
-
- return err
-}
-
-func batchResultName(err error) string {
- updateErr, ok := errors.AsType[*updateContextError](err)
- if !ok {
- return ""
- }
-
- return updateErr.name
-}
diff --git a/ref/store/files/batch_test.go b/ref/store/files/batch_test.go
deleted file mode 100644
index 80575e5e..00000000
--- a/ref/store/files/batch_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package files_test
-
-import (
- "errors"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestBatchApplyRejectsStaleDeleteAndAppliesIndependentDelete(t *testing.T) {
- t.Parallel()
-
- //nolint:thelper
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- _, _, commitID := testRepo.MakeCommit(t, "base")
- _, _, staleID := testRepo.MakeCommit(t, "stale")
- testRepo.UpdateRef(t, "refs/heads/main", commitID)
- testRepo.UpdateRef(t, "refs/heads/topic", commitID)
-
- store := openFilesStore(t, testRepo, algo)
-
- batch, err := store.BeginBatch()
- if err != nil {
- t.Fatalf("BeginBatch: %v", err)
- }
-
- err = batch.Delete("refs/heads/main", staleID)
- if err != nil {
- t.Fatalf("Delete(main) queue: %v", err)
- }
-
- err = batch.Delete("refs/heads/topic", commitID)
- if err != nil {
- t.Fatalf("Delete(topic) queue: %v", err)
- }
-
- results, err := batch.Apply()
- if err != nil {
- t.Fatalf("Apply: %v", err)
- }
-
- if len(results) != 2 {
- t.Fatalf("len(results) = %d, want 2", len(results))
- }
-
- if results[0].Status != refstore.BatchStatusRejected {
- t.Fatalf("results[0].Status = %v, want rejected", results[0].Status)
- }
-
- if _, ok := errors.AsType[*refstore.IncorrectOldValueError](results[0].Error); !errors.Is(results[0].Error, refstore.ErrReferenceNotFound) && !ok {
- t.Fatalf("results[0].Error = %v, want stale-value rejection", results[0].Error)
- }
-
- if results[1].Status != refstore.BatchStatusApplied {
- t.Fatalf("results[1].Status = %v, want applied", results[1].Status)
- }
-
- _, err = store.Resolve("refs/heads/main")
- if err != nil {
- t.Fatalf("Resolve(main): %v", err)
- }
-
- _, err = store.Resolve("refs/heads/topic")
- if err == nil {
- t.Fatal("refs/heads/topic still exists")
- }
- })
-}
-
-func TestBatchApplyRejectsDuplicateQueuedRef(t *testing.T) {
- t.Parallel()
-
- //nolint:thelper
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- _, _, commitID := testRepo.MakeCommit(t, "base")
- testRepo.UpdateRef(t, "refs/heads/main", commitID)
-
- store := openFilesStore(t, testRepo, algo)
-
- batch, err := store.BeginBatch()
- if err != nil {
- t.Fatalf("BeginBatch: %v", err)
- }
-
- err = batch.Delete("refs/heads/main", commitID)
- if err != nil {
- t.Fatalf("Delete(main) queue: %v", err)
- }
-
- err = batch.Verify("refs/heads/main", commitID)
- if err != nil {
- t.Fatalf("Verify(main) queue: %v", err)
- }
-
- results, err := batch.Apply()
- if err != nil {
- t.Fatalf("Apply: %v", err)
- }
-
- if len(results) != 2 {
- t.Fatalf("len(results) = %d, want 2", len(results))
- }
-
- if results[0].Status != refstore.BatchStatusApplied {
- t.Fatalf("results[0].Status = %v, want applied", results[0].Status)
- }
-
- if results[1].Status != refstore.BatchStatusRejected {
- t.Fatalf("results[1].Status = %v, want rejected", results[1].Status)
- }
-
- if _, ok := errors.AsType[*refstore.DuplicateUpdateError](results[1].Error); !ok {
- t.Fatalf("results[1].Error = %v, want duplicate update error", results[1].Error)
- }
-
- _, err = store.Resolve("refs/heads/main")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(main): %v", err)
- }
- })
-}
diff --git a/ref/store/files/broken_ref_error.go b/ref/store/files/broken_ref_error.go
deleted file mode 100644
index daa40849..00000000
--- a/ref/store/files/broken_ref_error.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package files
-
-import "fmt"
-
-type brokenRefError struct {
- name string
- err error
-}
-
-func (err brokenRefError) Error() string {
- return fmt.Sprintf("refstore/files: broken reference %q: %v", err.name, err.err)
-}
-
-func (err brokenRefError) Unwrap() error {
- return err.err
-}
diff --git a/ref/store/files/close.go b/ref/store/files/close.go
deleted file mode 100644
index 84a69dab..00000000
--- a/ref/store/files/close.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package files
-
-// Close releases resources associated with the store.
-//
-// Labels: MT-Unsafe.
-func (store *Store) Close() error {
- return store.commonRoot.Close()
-}
diff --git a/ref/store/files/helpers_test.go b/ref/store/files/helpers_test.go
deleted file mode 100644
index c46cc9fc..00000000
--- a/ref/store/files/helpers_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package files_test
-
-import (
- "os"
- "slices"
- "strings"
- "testing"
- "time"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref/store/files"
-)
-
-const testPackedRefsTimeout = time.Second
-
-func openFilesStore(t *testing.T, testRepo *testgit.TestRepo, algo objectid.Algorithm) *files.Store {
- t.Helper()
-
- root := testRepo.OpenGitRoot(t)
-
- store, err := files.New(root, algo, testPackedRefsTimeout)
- if err != nil {
- t.Fatalf("files.New: %v", err)
- }
-
- return store
-}
-
-func openFilesStoreAt(t *testing.T, root *os.Root, algo objectid.Algorithm) *files.Store {
- t.Helper()
-
- store, err := files.New(root, algo, testPackedRefsTimeout)
- if err != nil {
- t.Fatalf("files.New: %v", err)
- }
-
- return store
-}
-
-func openGitRootUnder(t *testing.T, repoRoot *os.Root, worktreeName string) *os.Root {
- t.Helper()
-
- worktreeRoot, err := repoRoot.OpenRoot(worktreeName)
- if err != nil {
- t.Fatalf("OpenRoot(%q): %v", worktreeName, err)
- }
-
- t.Cleanup(func() {
- _ = worktreeRoot.Close()
- })
-
- info, err := worktreeRoot.Stat(".git")
- if err != nil {
- t.Fatalf("stat %q: %v", worktreeName+"/.git", err)
- }
-
- if info.IsDir() {
- gitRoot, err := worktreeRoot.OpenRoot(".git")
- if err != nil {
- t.Fatalf("OpenRoot(.git): %v", err)
- }
-
- t.Cleanup(func() {
- _ = gitRoot.Close()
- })
-
- return gitRoot
- }
-
- content, err := worktreeRoot.ReadFile(".git")
- if err != nil {
- t.Fatalf("read %q: %v", worktreeName+"/.git", err)
- }
-
- gitDir := strings.TrimSpace(strings.TrimPrefix(string(content), "gitdir:"))
- if gitDir == "" {
- t.Fatalf("%q does not contain a gitdir path", worktreeName+"/.git")
- }
-
- if strings.HasPrefix(gitDir, "/") {
- gitRoot, err := os.OpenRoot(gitDir)
- if err != nil {
- t.Fatalf("os.OpenRoot(%q): %v", gitDir, err)
- }
-
- t.Cleanup(func() {
- _ = gitRoot.Close()
- })
-
- return gitRoot
- }
-
- gitRoot, err := worktreeRoot.OpenRoot(gitDir)
- if err != nil {
- t.Fatalf("os.OpenRoot(%q): %v", gitDir, err)
- }
-
- t.Cleanup(func() {
- _ = gitRoot.Close()
- })
-
- return gitRoot
-}
-
-func assertListMatchesGitForEachRef(t *testing.T, gitOut string, store *files.Store) {
- t.Helper()
-
- listed, err := store.List("")
- if err != nil {
- t.Fatalf("List(\"\"): %v", err)
- }
-
- gotNames := make([]string, 0, len(listed))
- for _, got := range listed {
- if got.Name() == "HEAD" {
- continue
- }
-
- gotNames = append(gotNames, got.Name())
- }
-
- slices.Sort(gotNames)
-
- wantLines := strings.Split(strings.TrimSpace(gitOut), "\n")
- wantNames := make([]string, 0, len(wantLines))
-
- for _, line := range wantLines {
- line = strings.TrimSpace(line)
- if line == "" {
- continue
- }
-
- wantNames = append(wantNames, line)
- }
-
- slices.Sort(wantNames)
-
- if !slices.Equal(gotNames, wantNames) {
- t.Fatalf("List names = %v, want %v", gotNames, wantNames)
- }
-}
-
-func forEachRefLines(output string) []string {
- if strings.TrimSpace(output) == "" {
- return nil
- }
-
- return strings.Split(strings.TrimSpace(output), "\n")
-}
diff --git a/ref/store/files/new.go b/ref/store/files/new.go
deleted file mode 100644
index 391930fb..00000000
--- a/ref/store/files/new.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package files
-
-import (
- "math/rand"
- "os"
- "time"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
-)
-
-// New creates one files ref store rooted at one repository gitdir.
-//
-// Labels: Deps-Borrowed, Life-Parent.
-func New(root *os.Root, algo objectid.Algorithm, packedRefsTimeout time.Duration) (*Store, error) {
- if algo.Size() == 0 {
- return nil, objectid.ErrInvalidAlgorithm
- }
-
- commonRoot, err := openCommonRoot(root)
- if err != nil {
- return nil, err
- }
-
- return &Store{
- gitRoot: root,
- commonRoot: commonRoot,
- algo: algo,
- lockRand: rand.New(rand.NewSource(time.Now().UnixNano())), //nolint:gosec
- packedRefsTimeout: packedRefsTimeout,
- }, nil
-}
diff --git a/ref/store/files/packed_delete_test.go b/ref/store/files/packed_delete_test.go
deleted file mode 100644
index 184eb79c..00000000
--- a/ref/store/files/packed_delete_test.go
+++ /dev/null
@@ -1,292 +0,0 @@
-package files_test
-
-import (
- "errors"
- "os"
- "slices"
- "sync"
- "testing"
- "time"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesTransactionPackedDeleteFailureLeavesRefsUnchanged(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Run("packed-refs.lock held", func(t *testing.T) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, packedID := testRepo.MakeCommit(t, "packed")
- _, _, looseID := testRepo.MakeCommit(t, "loose")
- prefix := "refs/locked-packed-refs"
-
- testRepo.UpdateRef(t, prefix+"/foo", packedID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.UpdateRef(t, prefix+"/foo", looseID)
- unchanged := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(objectname) %(refname)", prefix))
- testRepo.WriteFile(t, "packed-refs.lock", []byte{}, 0o644)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(lock held): %v", err)
- }
-
- err = tx.Delete(prefix+"/foo", looseID)
- if err != nil {
- t.Fatalf("Delete(lock held) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(lock held) unexpectedly succeeded")
- }
-
- actual := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(objectname) %(refname)", prefix))
- if !slices.Equal(actual, unchanged) {
- t.Fatalf("ShowRef after failed delete = %v, want %v", actual, unchanged)
- }
-
- got, err := store.ResolveToDetached(prefix + "/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(lock held): %v", err)
- }
-
- if got.ID != looseID {
- t.Fatalf("ResolveToDetached(lock held) = %s, want %s", got.ID, looseID)
- }
-
- gitRoot := testRepo.OpenGitRoot(t)
-
- _, statErr := gitRoot.Stat(prefix + "/foo.lock")
- if !errors.Is(statErr, os.ErrNotExist) {
- t.Fatalf("unexpected leftover loose lock: %v", statErr)
- }
- })
-
- t.Run("packed-refs.new exists", func(t *testing.T) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, packedID := testRepo.MakeCommit(t, "packed")
- _, _, looseID := testRepo.MakeCommit(t, "loose")
- prefix := "refs/failed-packed-refs"
-
- testRepo.UpdateRef(t, prefix+"/foo", packedID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.UpdateRef(t, prefix+"/foo", looseID)
- unchanged := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(objectname) %(refname)", prefix))
- testRepo.WriteFile(t, "packed-refs.new", []byte{}, 0o644)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(new exists): %v", err)
- }
-
- err = tx.Delete(prefix+"/foo", looseID)
- if err != nil {
- t.Fatalf("Delete(new exists) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(new exists) unexpectedly succeeded")
- }
-
- actual := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(objectname) %(refname)", prefix))
- if !slices.Equal(actual, unchanged) {
- t.Fatalf("ShowRef after failed delete = %v, want %v", actual, unchanged)
- }
-
- got, err := store.ResolveToDetached(prefix + "/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(new exists): %v", err)
- }
-
- if got.ID != looseID {
- t.Fatalf("ResolveToDetached(new exists) = %s, want %s", got.ID, looseID)
- }
- })
- })
-}
-
-func TestFilesPackedRefDeleteDoesNotCreateDirectories(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, commitID := testRepo.MakeCommit(t, "packed-only")
- name := "refs/heads/d1/d2/r1"
-
- testRepo.UpdateRef(t, name, commitID)
- testRepo.PackRefs(t, "--all", "--prune")
-
- gitRoot := testRepo.OpenGitRoot(t)
-
- _, err := gitRoot.Stat("refs/heads/d1/d2")
- if !errors.Is(err, os.ErrNotExist) {
- t.Fatalf("refs/heads/d1/d2 unexpectedly exists before delete: %v", err)
- }
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tx.Delete(name, commitID)
- if err != nil {
- t.Fatalf("Delete queue: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit: %v", err)
- }
-
- _, err = gitRoot.Stat("refs/heads/d1/d2")
- if !errors.Is(err, os.ErrNotExist) {
- t.Fatalf("refs/heads/d1/d2 unexpectedly exists after delete: %v", err)
- }
-
- _, err = gitRoot.Stat("refs/heads/d1")
- if !errors.Is(err, os.ErrNotExist) {
- t.Fatalf("refs/heads/d1 unexpectedly exists after delete: %v", err)
- }
- })
-}
-
-func TestFilesPackedRefIgnoresEmptyDirectories(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, commitID := testRepo.MakeCommit(t, "packed-visible")
- prefix := "refs/e-for-each-ref"
- name := prefix + "/foo"
-
- testRepo.UpdateRef(t, name, commitID)
- expected := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(objectname) %(refname)", prefix))
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.WriteFileAll(t, prefix+"/foo/bar/baz/.keep", []byte{}, 0o755, 0o644)
- testRepo.Remove(t, prefix+"/foo/bar/baz/.keep")
-
- store := openFilesStore(t, testRepo, algo)
-
- got, err := store.ResolveToDetached(name)
- if err != nil {
- t.Fatalf("ResolveToDetached: %v", err)
- }
-
- if got.ID != commitID {
- t.Fatalf("ResolveToDetached = %s, want %s", got.ID, commitID)
- }
-
- actual := make([]string, 0)
-
- listed, err := store.List(prefix + "/*")
- if err != nil {
- t.Fatalf("List: %v", err)
- }
-
- for _, entry := range listed {
- actual = append(actual, entry.Name())
- }
-
- fullActual := make([]string, 0, len(actual))
- for _, name := range actual {
- refValue, resolveErr := store.ResolveToDetached(name)
- if resolveErr != nil {
- t.Fatalf("ResolveToDetached(%q): %v", name, resolveErr)
- }
-
- fullActual = append(fullActual, refValue.ID.String()+" "+name)
- }
-
- slices.Sort(fullActual)
-
- if !slices.Equal(fullActual, expected) {
- t.Fatalf("for-each-ref view = %v, want %v", fullActual, expected)
- }
- })
-}
-
-func TestFilesDeleteWaitsForPackedRefsLockWithoutIntermediateState(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, packedID := testRepo.MakeCommit(t, "packed")
- _, _, looseID := testRepo.MakeCommit(t, "loose")
- prefix := "refs/slow-transaction"
-
- testRepo.UpdateRef(t, prefix+"/foo", packedID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.UpdateRef(t, prefix+"/foo", looseID)
- testRepo.WriteFile(t, "packed-refs.lock", []byte{}, 0o644)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tx.Delete(prefix+"/foo", looseID)
- if err != nil {
- t.Fatalf("Delete queue: %v", err)
- }
-
- done := make(chan error, 1)
-
- var wg sync.WaitGroup
-
- wg.Go(func() {
- done <- tx.Commit()
- })
-
- time.Sleep(75 * time.Millisecond)
-
- select {
- case err := <-done:
- t.Fatalf("Commit finished too early: %v", err)
- default:
- }
-
- got, err := store.ResolveToDetached(prefix + "/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached while lock held: %v", err)
- }
-
- if got.ID != looseID {
- t.Fatalf("ResolveToDetached while lock held = %s, want %s", got.ID, looseID)
- }
-
- testRepo.Remove(t, "packed-refs.lock")
-
- select {
- case err := <-done:
- if err != nil {
- t.Fatalf("Commit after lock release: %v", err)
- }
- case <-time.After(2 * time.Second):
- t.Fatal("Commit did not finish after lock release")
- }
-
- wg.Wait()
-
- _, err = store.Resolve(prefix + "/foo")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve after delete error = %v, want ErrReferenceNotFound", err)
- }
- })
-}
diff --git a/ref/store/files/packed_parse.go b/ref/store/files/packed_parse.go
deleted file mode 100644
index 3662f6ed..00000000
--- a/ref/store/files/packed_parse.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package files
-
-import (
- "bufio"
- "fmt"
- "io"
- "strings"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref"
-)
-
-func parsePackedRefs(r io.Reader, algo objectid.Algorithm) (map[string]ref.Detached, []ref.Detached, error) {
- byName := make(map[string]ref.Detached)
- ordered := make([]ref.Detached, 0, 32)
-
- br := bufio.NewReader(r)
- prev := -1
- lineNum := 0
- hexsz := algo.Size() * 2
-
- for {
- line, err := br.ReadString('\n')
- if err != nil && err != io.EOF {
- return nil, nil, err
- }
-
- if line == "" && err == io.EOF {
- break
- }
-
- lineNum++
- hadNewline := strings.HasSuffix(line, "\n")
- line = strings.TrimSuffix(line, "\n")
-
- if err == io.EOF && !hadNewline {
- return nil, nil, fmt.Errorf("refstore/files: line %d: unterminated line", lineNum)
- }
-
- if line == "" || strings.HasPrefix(line, "#") {
- if err == io.EOF {
- break
- }
-
- continue
- }
-
- if strings.HasPrefix(line, "^") {
- if prev < 0 {
- return nil, nil, fmt.Errorf("refstore/files: line %d: peeled line without preceding ref", lineNum)
- }
-
- if len(line) != hexsz+1 {
- return nil, nil, fmt.Errorf("refstore/files: line %d: malformed peeled line", lineNum)
- }
-
- peeled, parseErr := objectid.ParseHex(algo, line[1:])
- if parseErr != nil {
- return nil, nil, fmt.Errorf("refstore/files: line %d: invalid peeled oid: %w", lineNum, parseErr)
- }
-
- peeledCopy := peeled
- cur := ordered[prev]
- cur.Peeled = &peeledCopy
- ordered[prev] = cur
- byName[cur.Name()] = cur
-
- if err == io.EOF {
- break
- }
-
- continue
- }
-
- if len(line) < hexsz+2 {
- return nil, nil, fmt.Errorf("refstore/files: line %d: malformed entry", lineNum)
- }
-
- if line[hexsz] != ' ' {
- return nil, nil, fmt.Errorf("refstore/files: line %d: malformed entry", lineNum)
- }
-
- idText := line[:hexsz]
-
- name := line[hexsz+1:]
- if name == "" {
- return nil, nil, fmt.Errorf("refstore/files: line %d: empty ref name", lineNum)
- }
-
- id, parseErr := objectid.ParseHex(algo, idText)
- if parseErr != nil {
- return nil, nil, fmt.Errorf("refstore/files: line %d: invalid oid: %w", lineNum, parseErr)
- }
-
- if _, exists := byName[name]; exists {
- return nil, nil, fmt.Errorf("refstore/files: line %d: duplicate ref %q", lineNum, name)
- }
-
- detached := ref.Detached{
- RefName: name,
- ID: id,
- }
- ordered = append(ordered, detached)
- prev = len(ordered) - 1
- byName[name] = detached
-
- if err == io.EOF {
- break
- }
- }
-
- return byName, ordered, nil
-}
diff --git a/ref/store/files/packed_read.go b/ref/store/files/packed_read.go
deleted file mode 100644
index 20800709..00000000
--- a/ref/store/files/packed_read.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package files
-
-import (
- "errors"
- "fmt"
- "os"
-
- "codeberg.org/lindenii/furgit/ref"
-)
-
-func (store *Store) readPackedRefs() (*packedRefs, error) {
- file, err := store.commonRoot.Open("packed-refs")
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return &packedRefs{
- byName: make(map[string]ref.Detached),
- ordered: nil,
- }, nil
- }
-
- return nil, fmt.Errorf("refstore/files: open packed-refs: %w", err)
- }
-
- defer func() { _ = file.Close() }()
-
- byName, ordered, err := parsePackedRefs(file, store.algo)
- if err != nil {
- return nil, err
- }
-
- return &packedRefs{
- byName: byName,
- ordered: ordered,
- }, nil
-}
diff --git a/ref/store/files/packed_refs.go b/ref/store/files/packed_refs.go
deleted file mode 100644
index f3e91d83..00000000
--- a/ref/store/files/packed_refs.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package files
-
-import (
- "codeberg.org/lindenii/furgit/ref"
-)
-
-type packedRefs struct {
- byName map[string]ref.Detached
- ordered []ref.Detached
-}
diff --git a/ref/store/files/read_list.go b/ref/store/files/read_list.go
deleted file mode 100644
index 5a828276..00000000
--- a/ref/store/files/read_list.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package files
-
-import (
- "errors"
- "path"
- "slices"
-
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// List lists references from the visible files ref namespace.
-func (store *Store) List(pattern string) ([]ref.Ref, error) {
- matchAll := pattern == ""
- if !matchAll {
- _, err := path.Match(pattern, "HEAD")
- if err != nil {
- return nil, err
- }
- }
-
- looseNames, err := store.collectLooseRefNames()
- if err != nil {
- return nil, err
- }
-
- packed, err := store.readPackedRefs()
- if err != nil {
- return nil, err
- }
-
- byName := make(map[string]ref.Ref, len(looseNames)+len(packed.byName))
- for _, detached := range packed.ordered {
- byName[detached.Name()] = detached
- }
-
- for _, name := range looseNames {
- resolved, resolveErr := store.readLooseRef(name)
- if resolveErr != nil {
- if errors.Is(resolveErr, refstore.ErrReferenceNotFound) {
- delete(byName, name)
-
- continue
- }
-
- return nil, resolveErr
- }
-
- byName[name] = resolved
- }
-
- names := make([]string, 0, len(byName))
- for name := range byName {
- if !matchAll {
- matched, matchErr := path.Match(pattern, name)
- if matchErr != nil {
- return nil, matchErr
- }
-
- if !matched {
- continue
- }
- }
-
- names = append(names, name)
- }
-
- slices.Sort(names)
-
- refs := make([]ref.Ref, 0, len(names))
- for _, name := range names {
- refs = append(refs, byName[name])
- }
-
- return refs, nil
-}
diff --git a/ref/store/files/read_list_collect.go b/ref/store/files/read_list_collect.go
deleted file mode 100644
index f4e2cb69..00000000
--- a/ref/store/files/read_list_collect.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
- "path"
- "strings"
-)
-
-func (store *Store) collectLooseRefNames() ([]string, error) {
- names := make([]string, 0, 16)
- seen := make(map[string]struct{}, 16)
-
- _, err := store.gitRoot.Stat("HEAD")
- if err == nil {
- names = append(names, "HEAD")
- seen["HEAD"] = struct{}{}
- } else if !errors.Is(err, os.ErrNotExist) {
- return nil, err
- }
-
- var walk func(*os.Root, string) error
-
- walk = func(root *os.Root, dir string) error {
- file, openErr := root.Open(dir)
- if openErr != nil {
- if errors.Is(openErr, os.ErrNotExist) {
- return nil
- }
-
- return openErr
- }
-
- defer func() { _ = file.Close() }()
-
- entries, readErr := file.ReadDir(-1)
- if readErr != nil {
- return readErr
- }
-
- for _, entry := range entries {
- name := path.Join(dir, entry.Name())
- if entry.IsDir() {
- err := walk(root, name)
- if err != nil {
- return err
- }
-
- continue
- }
-
- if strings.HasSuffix(name, ".lock") {
- continue
- }
-
- if _, ok := seen[name]; ok {
- continue
- }
-
- seen[name] = struct{}{}
- names = append(names, name)
- }
-
- return nil
- }
-
- err = walk(store.commonRoot, "refs")
- if err != nil {
- return nil, err
- }
-
- err = walk(store.gitRoot, "refs")
- if err != nil {
- return nil, err
- }
-
- return names, nil
-}
diff --git a/ref/store/files/read_loose.go b/ref/store/files/read_loose.go
deleted file mode 100644
index e9b1435a..00000000
--- a/ref/store/files/read_loose.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package files
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (store *Store) readLooseRef(name string) (ref.Ref, error) { //nolint:ireturn
- refPath := store.loosePath(name)
-
- data, err := store.rootFor(refPath.root).ReadFile(refPath.path)
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return nil, refstore.ErrReferenceNotFound
- }
-
- return nil, err
- }
-
- line := strings.TrimRightFunc(string(data), isRefWhitespace)
- if strings.HasPrefix(line, "ref:") {
- target := strings.TrimLeftFunc(line[len("ref:"):], isRefWhitespace)
- if target == "" {
- return nil, brokenRefError{name: name, err: fmt.Errorf("empty symbolic target")}
- }
-
- return ref.Symbolic{
- RefName: name,
- Target: target,
- }, nil
- }
-
- id, err := objectid.ParseHex(store.algo, line)
- if err != nil {
- return nil, brokenRefError{name: name, err: err}
- }
-
- return ref.Detached{
- RefName: name,
- ID: id,
- }, nil
-}
diff --git a/ref/store/files/read_resolve.go b/ref/store/files/read_resolve.go
deleted file mode 100644
index a998f970..00000000
--- a/ref/store/files/read_resolve.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package files
-
-import (
- "errors"
-
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// Resolve resolves one reference name from the files store visible namespace.
-func (store *Store) Resolve(name string) (ref.Ref, error) { //nolint:ireturn
- if name == "" {
- return nil, refstore.ErrReferenceNotFound
- }
-
- resolved, err := store.readLooseRef(name)
- if err == nil {
- return resolved, nil
- }
-
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- refPath := store.loosePath(name)
-
- info, statErr := store.rootFor(refPath.root).Stat(refPath.path)
- if statErr != nil || !info.IsDir() {
- return nil, err
- }
- }
-
- packed, packedErr := store.readPackedRefs()
- if packedErr != nil {
- return nil, packedErr
- }
-
- detached, ok := packed.byName[name]
- if !ok {
- return nil, refstore.ErrReferenceNotFound
- }
-
- return detached, nil
-}
diff --git a/ref/store/files/read_resolve_fully.go b/ref/store/files/read_resolve_fully.go
deleted file mode 100644
index de58eb6d..00000000
--- a/ref/store/files/read_resolve_fully.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package files
-
-import (
- "fmt"
- "strings"
-
- "codeberg.org/lindenii/furgit/ref"
-)
-
-// ResolveToDetached resolves symbolic references through the visible files store
-// namespace until one detached reference is reached.
-func (store *Store) ResolveToDetached(name string) (ref.Detached, error) {
- cur := name
- seen := make(map[string]struct{})
-
- for {
- if _, ok := seen[cur]; ok {
- return ref.Detached{}, fmt.Errorf("refstore/files: symbolic reference cycle at %q", cur)
- }
-
- seen[cur] = struct{}{}
-
- resolved, err := store.Resolve(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/files: symbolic reference %q has empty target", resolved.Name())
- }
-
- cur = target
- default:
- return ref.Detached{}, fmt.Errorf("refstore/files: unsupported reference type %T", resolved)
- }
- }
-}
diff --git a/ref/store/files/resolve_list_test.go b/ref/store/files/resolve_list_test.go
deleted file mode 100644
index d52c5aa2..00000000
--- a/ref/store/files/resolve_list_test.go
+++ /dev/null
@@ -1,269 +0,0 @@
-package files_test
-
-import (
- "slices"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref"
-)
-
-func TestFilesResolveAndListOverlay(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, packedID := testRepo.MakeCommit(t, "packed base")
- _, _, looseID := testRepo.MakeCommit(t, "loose override")
- testRepo.UpdateRef(t, "refs/heads/main", packedID)
- testRepo.UpdateRef(t, "refs/tags/v1", packedID)
- testRepo.SymbolicRef(t, "HEAD", "refs/heads/main")
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.UpdateRef(t, "refs/heads/main", looseID)
- testRepo.UpdateRef(t, "refs/heads/dev", looseID)
-
- store := openFilesStore(t, testRepo, algo)
-
- resolvedMain, err := store.Resolve("refs/heads/main")
- if err != nil {
- t.Fatalf("Resolve(main): %v", err)
- }
-
- mainDet, ok := resolvedMain.(ref.Detached)
- if !ok {
- t.Fatalf("Resolve(main) type = %T, want ref.Detached", resolvedMain)
- }
-
- if mainDet.ID != looseID {
- t.Fatalf("Resolve(main) id = %s, want %s", mainDet.ID, looseID)
- }
-
- resolvedHead, err := store.Resolve("HEAD")
- if err != nil {
- t.Fatalf("Resolve(HEAD): %v", err)
- }
-
- headSym, ok := resolvedHead.(ref.Symbolic)
- if !ok {
- t.Fatalf("Resolve(HEAD) type = %T, want ref.Symbolic", resolvedHead)
- }
-
- if headSym.Target != "refs/heads/main" {
- t.Fatalf("Resolve(HEAD) target = %q, want %q", headSym.Target, "refs/heads/main")
- }
-
- fullHead, err := store.ResolveToDetached("HEAD")
- if err != nil {
- t.Fatalf("ResolveToDetached(HEAD): %v", err)
- }
-
- if fullHead.ID != looseID {
- t.Fatalf("ResolveToDetached(HEAD) = %s, want %s", fullHead.ID, looseID)
- }
-
- allRefs, err := store.List("")
- if err != nil {
- t.Fatalf("List(\"\"): %v", err)
- }
-
- names := make([]string, 0, len(allRefs))
- for _, entry := range allRefs {
- names = append(names, entry.Name())
- }
-
- slices.Sort(names)
-
- want := []string{"HEAD", "refs/heads/dev", "refs/heads/main", "refs/tags/v1"}
- if !slices.Equal(names, want) {
- t.Fatalf("List(\"\") names = %v, want %v", names, want)
- }
- })
-}
-
-func TestFilesLooseRefParsingMatchesGit(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- oid := testRepo.HashObject(t, "blob", []byte("payload\n"))
-
- testRepo.WriteFileAll(t, ".git/refs/heads/no-lf", []byte(oid.String()), 0o755, 0o644)
- testRepo.WriteFileAll(t, ".git/refs/heads/trailing-ws", []byte(oid.String()+" "), 0o755, 0o644)
- testRepo.WriteFileAll(t, ".git/refs/heads/leading-ws", []byte(" "+oid.String()+"\n"), 0o755, 0o644)
- testRepo.WriteFileAll(t, ".git/refs/heads/sym-trailing", []byte("ref: refs/heads/main "), 0o755, 0o644)
- testRepo.WriteFileAll(t, ".git/refs/heads/sym-leading", []byte(" ref: refs/heads/main\n"), 0o755, 0o644)
-
- store := openFilesStore(t, testRepo, algo)
-
- got, err := store.ResolveToDetached("refs/heads/no-lf")
- if err != nil {
- t.Fatalf("ResolveToDetached(no-lf): %v", err)
- }
-
- if got.ID != oid {
- t.Fatalf("ResolveToDetached(no-lf) = %s, want %s", got.ID, oid)
- }
-
- got, err = store.ResolveToDetached("refs/heads/trailing-ws")
- if err != nil {
- t.Fatalf("ResolveToDetached(trailing-ws): %v", err)
- }
-
- if got.ID != oid {
- t.Fatalf("ResolveToDetached(trailing-ws) = %s, want %s", got.ID, oid)
- }
-
- _, err = store.Resolve("refs/heads/leading-ws")
- if err == nil {
- t.Fatal("Resolve(leading-ws) unexpectedly succeeded")
- }
-
- resolved, err := store.Resolve("refs/heads/sym-trailing")
- if err != nil {
- t.Fatalf("Resolve(sym-trailing): %v", err)
- }
-
- sym, ok := resolved.(ref.Symbolic)
- if !ok {
- t.Fatalf("Resolve(sym-trailing) type = %T, want ref.Symbolic", resolved)
- }
-
- if sym.Target != "refs/heads/main" {
- t.Fatalf("Resolve(sym-trailing) target = %q, want %q", sym.Target, "refs/heads/main")
- }
-
- _, err = store.Resolve("refs/heads/sym-leading")
- if err == nil {
- t.Fatal("Resolve(sym-leading) unexpectedly succeeded")
- }
- })
-}
-
-func TestFilesRejectMalformedPackedRefs(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true, RefFormat: "files"})
- _, _, commitID := testRepo.MakeCommit(t, "packed")
- testRepo.UpdateRef(t, "refs/heads/main", commitID)
- testRepo.PackRefs(t, "--all", "--prune")
-
- hex := commitID.String()
-
- cases := []struct {
- name string
- content string
- }{
- {
- name: "unterminated line",
- content: "# pack-refs with: peeled fully-peeled sorted\n" + hex + " refs/heads/main",
- },
- {
- name: "junk line",
- content: "# pack-refs with: peeled fully-peeled sorted\nbogus content\n",
- },
- {
- name: "short oid",
- content: "# pack-refs with: peeled fully-peeled sorted\n" + hex[:7] + " refs/heads/main\n",
- },
- {
- name: "trailing garbage after oid",
- content: "# pack-refs with: peeled fully-peeled sorted\n" + hex + "xrefs/heads/main\n",
- },
- {
- name: "malformed peeled line",
- content: "# pack-refs with: peeled fully-peeled sorted\n" + hex + " refs/tags/v1\n^" + hex + " garbage\n",
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- testRepo.WriteFile(t, "packed-refs", []byte(tc.content), 0o644)
- store := openFilesStore(t, testRepo, algo)
-
- _, err := store.List("")
- if err == nil {
- t.Fatal("List unexpectedly succeeded")
- }
- })
- }
- })
-}
-
-func TestFilesPackedRefsReadSemanticsMatchGit(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Run("stale packed entry is still readable", func(t *testing.T) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- testRepo.Run(t, "commit", "--allow-empty", "-m", "one")
-
- oneID, err := objectid.ParseHex(algo, testRepo.Run(t, "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(one): %v", err)
- }
-
- testRepo.Run(t, "tag", "-a", "v1.0", "-m", "v1.0", "HEAD")
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.Run(t, "checkout", "--orphan", "another")
- testRepo.Run(t, "commit", "--allow-empty", "-m", "two")
- testRepo.Run(t, "checkout", "-B", "main")
- testRepo.Run(t, "branch", "-D", "another")
- testRepo.Run(t, "reflog", "expire", "--expire=now", "--all")
- testRepo.Run(t, "prune")
-
- store := openFilesStore(t, testRepo, algo)
-
- got, err := store.ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if got.ID == oneID {
- t.Fatalf("ResolveToDetached(main) unexpectedly returned stale packed id %s", oneID)
- }
-
- tagRef, err := store.Resolve("refs/tags/v1.0")
- if err != nil {
- t.Fatalf("Resolve(tag): %v", err)
- }
-
- tagDet, ok := tagRef.(ref.Detached)
- if !ok {
- t.Fatalf("Resolve(tag) type = %T, want ref.Detached", tagRef)
- }
-
- if tagDet.ID.Algorithm().Size() == 0 {
- t.Fatal("Resolve(tag) returned zero object id")
- }
- })
-
- t.Run("exact unicode packed ref remains enumerable", func(t *testing.T) {
- t.Parallel()
-
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- _, _, commitID := testRepo.MakeCommit(t, "unicode")
- testRepo.UpdateRef(t, "refs/heads/\ue43f", commitID)
- testRepo.UpdateRef(t, "refs/heads/z", commitID)
- testRepo.PackRefs(t, "--all", "--prune")
-
- store := openFilesStore(t, testRepo, algo)
-
- listed, err := store.List("refs/heads/z")
- if err != nil {
- t.Fatalf("List(refs/heads/z): %v", err)
- }
-
- if len(listed) != 1 {
- t.Fatalf("List(refs/heads/z) len = %d, want 1", len(listed))
- }
-
- if listed[0].Name() != "refs/heads/z" {
- t.Fatalf("List(refs/heads/z)[0] = %q, want %q", listed[0].Name(), "refs/heads/z")
- }
- })
- })
-}
diff --git a/ref/store/files/root_for.go b/ref/store/files/root_for.go
deleted file mode 100644
index cb968ad9..00000000
--- a/ref/store/files/root_for.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package files
-
-import (
- "os"
-)
-
-func (store *Store) rootFor(kind rootKind) *os.Root {
- if kind == rootCommon {
- return store.commonRoot
- }
-
- return store.gitRoot
-}
diff --git a/ref/store/files/root_kind.go b/ref/store/files/root_kind.go
deleted file mode 100644
index d0ae8cf1..00000000
--- a/ref/store/files/root_kind.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package files
-
-type rootKind uint8
-
-const (
- rootGit rootKind = iota
- rootCommon
-)
diff --git a/ref/store/files/root_loose_path.go b/ref/store/files/root_loose_path.go
deleted file mode 100644
index 7764073b..00000000
--- a/ref/store/files/root_loose_path.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package files
-
-import (
- "path"
-
- "codeberg.org/lindenii/furgit/ref/name"
-)
-
-func (store *Store) loosePath(name string) refPath {
- parsed := refname.ParseWorktree(name)
- switch parsed.Type {
- case refname.WorktreeCurrent:
- return refPath{root: rootGit, path: parsed.BareRefName}
- case refname.WorktreeMain, refname.WorktreeShared:
- return refPath{root: rootCommon, path: parsed.BareRefName}
- case refname.WorktreeOther:
- return refPath{
- root: rootCommon,
- path: path.Join("worktrees", parsed.WorktreeName, parsed.BareRefName),
- }
- default:
- return refPath{root: rootCommon, path: name}
- }
-}
diff --git a/ref/store/files/root_open_common.go b/ref/store/files/root_open_common.go
deleted file mode 100644
index cac98cbc..00000000
--- a/ref/store/files/root_open_common.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
- "path/filepath"
- "strings"
-)
-
-func openCommonRoot(gitRoot *os.Root) (*os.Root, error) {
- content, err := gitRoot.ReadFile("commondir")
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return gitRoot.OpenRoot(".")
- }
-
- return nil, err
- }
-
- commonDir := strings.TrimSpace(string(content))
- if commonDir == "" {
- return nil, os.ErrNotExist
- }
-
- if filepath.IsAbs(commonDir) {
- return os.OpenRoot(commonDir)
- }
-
- // This is okay because that's how Git defines it anyway.
- return os.OpenRoot(filepath.Join(gitRoot.Name(), commonDir))
-}
diff --git a/ref/store/files/store.go b/ref/store/files/store.go
deleted file mode 100644
index 66d46d06..00000000
--- a/ref/store/files/store.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Package files provides one Git files ref store with loose-over-packed reads
-// and transaction-coordinated updates.
-package files
-
-import (
- "math/rand"
- "os"
- "time"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// Store reads and writes one Git files ref namespace rooted at one repository
-// gitdir plus its commondir.
-//
-// Labels: Close-Caller.
-type Store struct {
- gitRoot *os.Root
- commonRoot *os.Root
- algo objectid.Algorithm
- lockRand *rand.Rand
-
- packedRefsTimeout time.Duration
-}
-
-var (
- _ refstore.Reader = (*Store)(nil)
- _ refstore.Transactioner = (*Store)(nil)
- _ refstore.Batcher = (*Store)(nil)
-)
diff --git a/ref/store/files/transaction.go b/ref/store/files/transaction.go
deleted file mode 100644
index fec43e1d..00000000
--- a/ref/store/files/transaction.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package files
-
-import (
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// Transaction stages files-store updates for one atomic commit.
-type Transaction struct {
- store *Store
- ops []queuedUpdate
-}
-
-var _ refstore.Transaction = (*Transaction)(nil)
diff --git a/ref/store/files/transaction_abort.go b/ref/store/files/transaction_abort.go
deleted file mode 100644
index 4f8fed05..00000000
--- a/ref/store/files/transaction_abort.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package files
-
-// Abort abandons the queued updates.
-func (tx *Transaction) Abort() error { return nil }
diff --git a/ref/store/files/transaction_begin.go b/ref/store/files/transaction_begin.go
deleted file mode 100644
index cdd3bad1..00000000
--- a/ref/store/files/transaction_begin.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package files
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-// BeginTransaction creates one new files transaction.
-//
-//nolint:ireturn
-func (store *Store) BeginTransaction() (refstore.Transaction, error) {
- return &Transaction{
- store: store,
- ops: make([]queuedUpdate, 0, 8),
- }, nil
-}
diff --git a/ref/store/files/transaction_commit.go b/ref/store/files/transaction_commit.go
deleted file mode 100644
index aeea497e..00000000
--- a/ref/store/files/transaction_commit.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package files
-
-// Commit validates and applies the queued updates atomically.
-func (tx *Transaction) Commit() error {
- executor := &refUpdateExecutor{store: tx.store}
-
- prepared, err := executor.prepareUpdates(tx.ops)
- if err != nil {
- return err
- }
-
- return executor.commitPreparedUpdates(prepared)
-}
diff --git a/ref/store/files/transaction_dirs_test.go b/ref/store/files/transaction_dirs_test.go
deleted file mode 100644
index c010ae69..00000000
--- a/ref/store/files/transaction_dirs_test.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package files_test
-
-import (
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
-)
-
-func TestFilesTransactionEmptyDirectoriesDoNotBlock(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, oldID := testRepo.MakeCommit(t, "old")
- _, _, newID := testRepo.MakeCommit(t, "new")
-
- testRepo.UpdateRef(t, "refs/e-verify/foo", oldID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.WriteFileAll(t, "refs/e-verify/foo/bar/baz/.keep", []byte{}, 0o755, 0o644)
- testRepo.Remove(t, "refs/e-verify/foo/bar/baz/.keep")
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(verify): %v", err)
- }
-
- err = tx.Verify("refs/e-verify/foo", oldID)
- if err != nil {
- t.Fatalf("Verify with empty directories: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(verify with empty directories): %v", err)
- }
-
- testRepo.UpdateRef(t, "refs/e-update/foo", oldID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.WriteFileAll(t, "refs/e-update/foo/bar/baz/.keep", []byte{}, 0o755, 0o644)
- testRepo.Remove(t, "refs/e-update/foo/bar/baz/.keep")
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(update): %v", err)
- }
-
- err = tx.Update("refs/e-update/foo", newID, oldID)
- if err != nil {
- t.Fatalf("Update with empty directories: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(update with empty directories): %v", err)
- }
-
- got, err := store.ResolveToDetached("refs/e-update/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(updated foo): %v", err)
- }
-
- if got.ID != newID {
- t.Fatalf("updated foo = %s, want %s", got.ID, newID)
- }
-
- testRepo.WriteFileAll(t, "refs/e-create/foo/bar/baz/.keep", []byte{}, 0o755, 0o644)
- testRepo.Remove(t, "refs/e-create/foo/bar/baz/.keep")
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(create): %v", err)
- }
-
- err = tx.Create("refs/e-create/foo", oldID)
- if err != nil {
- t.Fatalf("Create with empty directories: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(create with empty directories): %v", err)
- }
-
- got, err = store.ResolveToDetached("refs/e-create/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(created foo): %v", err)
- }
-
- if got.ID != oldID {
- t.Fatalf("created foo = %s, want %s", got.ID, oldID)
- }
-
- testRepo.UpdateRef(t, "refs/e-delete/foo", oldID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.WriteFileAll(t, "refs/e-delete/foo/bar/baz/.keep", []byte{}, 0o755, 0o644)
- testRepo.Remove(t, "refs/e-delete/foo/bar/baz/.keep")
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete): %v", err)
- }
-
- err = tx.Delete("refs/e-delete/foo", oldID)
- if err != nil {
- t.Fatalf("Delete with empty directories: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(delete with empty directories): %v", err)
- }
- })
-}
-
-func TestFilesTransactionNonEmptyDirectoryAndBrokenRefBlockCreate(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, commitID := testRepo.MakeCommit(t, "base")
- store := openFilesStore(t, testRepo, algo)
-
- testRepo.WriteFileAll(t, "refs/ne-create/foo/bar/baz.lock", []byte(""), 0o755, 0o644)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(non-empty dir): %v", err)
- }
-
- err = tx.Create("refs/ne-create/foo", commitID)
- if err != nil {
- t.Fatalf("Create(non-empty dir) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(non-empty dir) unexpectedly succeeded")
- }
-
- testRepo.WriteFileAll(t, "refs/broken/foo", []byte("gobbledigook\n"), 0o755, 0o644)
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(broken ref): %v", err)
- }
-
- err = tx.Create("refs/broken/foo", commitID)
- if err != nil {
- t.Fatalf("Create(broken ref) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(broken ref) unexpectedly succeeded")
- }
- })
-}
-
-func TestFilesTransactionIndirectCreateMatchesGit(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Run("non-empty directory blocks", func(t *testing.T) {
- t.Parallel()
-
- repo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- _, _, innerID := repo.MakeCommit(t, "inner")
- prefix := "refs/ne-indirect-create"
-
- repo.SymbolicRef(t, prefix+"/symref", prefix+"/foo")
- repo.WriteFileAll(t, ".git/"+prefix+"/foo/bar/baz.lock", []byte{}, 0o755, 0o644)
- store := openFilesStore(t, repo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(non-empty): %v", err)
- }
-
- err = tx.Create(prefix+"/symref", innerID)
- if err != nil {
- t.Fatalf("Create(non-empty) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(non-empty) unexpectedly succeeded")
- }
- })
-
- t.Run("broken referent blocks", func(t *testing.T) {
- t.Parallel()
-
- repo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- _, _, commitID := repo.MakeCommit(t, "broken")
- prefix := "refs/broken-indirect-create"
-
- repo.SymbolicRef(t, prefix+"/symref", prefix+"/foo")
- repo.WriteFileAll(t, ".git/"+prefix+"/foo", []byte("gobbledigook\n"), 0o755, 0o644)
- store := openFilesStore(t, repo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(broken): %v", err)
- }
-
- err = tx.Create(prefix+"/symref", commitID)
- if err != nil {
- t.Fatalf("Create(broken) queue: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("Commit(broken) unexpectedly succeeded")
- }
- })
- })
-}
diff --git a/ref/store/files/transaction_names_test.go b/ref/store/files/transaction_names_test.go
deleted file mode 100644
index b362cb08..00000000
--- a/ref/store/files/transaction_names_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package files_test
-
-import (
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesTransactionValidateUpdateNames(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, commitID := testRepo.MakeCommit(t, "base")
-
- store := openFilesStore(t, testRepo, algo)
-
- tests := []struct {
- name string
- queue func(refstore.Transaction) error
- wantErr bool
- }{
- {
- name: "create refs/heads/main",
- queue: func(tx refstore.Transaction) error {
- return tx.Create("refs/heads/main", commitID)
- },
- },
- {
- name: "create foo/bar",
- queue: func(tx refstore.Transaction) error {
- return tx.Create("foo/bar", commitID)
- },
- },
- {
- name: "create FETCH_HEAD",
- queue: func(tx refstore.Transaction) error {
- return tx.Create("FETCH_HEAD", commitID)
- },
- wantErr: true,
- },
- {
- name: "create MERGE_HEAD",
- queue: func(tx refstore.Transaction) error {
- return tx.Create("MERGE_HEAD", commitID)
- },
- wantErr: true,
- },
- {
- name: "create bad refname",
- queue: func(tx refstore.Transaction) error {
- return tx.Create("refs/heads/.bad", commitID)
- },
- wantErr: true,
- },
- {
- name: "verify unsafe delete-style name",
- queue: func(tx refstore.Transaction) error {
- return tx.Verify("foo/bar", commitID)
- },
- wantErr: true,
- },
- {
- name: "verify pseudoref-style name",
- queue: func(tx refstore.Transaction) error {
- return tx.Verify("PSEUDOREF", commitID)
- },
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tt.queue(tx)
- if (err != nil) != tt.wantErr {
- t.Fatalf("queue err=%v, wantErr=%v", err, tt.wantErr)
- }
-
- _ = tx.Abort()
- })
- }
- })
-}
-
-func TestFilesTransactionSymbolicTargetRules(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, mainID := testRepo.MakeCommit(t, "main")
- testRepo.UpdateRef(t, "refs/heads/main", mainID)
- testRepo.UpdateRef(t, "ORIG_HEAD", mainID)
-
- store := openFilesStore(t, testRepo, algo)
-
- tests := []struct {
- name string
- queue func(refstore.Transaction) error
- wantErr bool
- }{
- {
- name: "head requires branch target",
- queue: func(tx refstore.Transaction) error {
- return tx.CreateSymbolic("HEAD", "foo")
- },
- wantErr: true,
- },
- {
- name: "head accepts refs/heads target",
- queue: func(tx refstore.Transaction) error {
- return tx.CreateSymbolic("HEAD", "refs/heads/main")
- },
- },
- {
- name: "non-head allows top-level target",
- queue: func(tx refstore.Transaction) error {
- return tx.CreateSymbolic("refs/heads/top-level", "ORIG_HEAD")
- },
- },
- {
- name: "non-head rejects invalid target",
- queue: func(tx refstore.Transaction) error {
- return tx.CreateSymbolic("refs/heads/invalid", "foo..bar")
- },
- wantErr: true,
- },
- {
- name: "non-head allows worktree target",
- queue: func(tx refstore.Transaction) error {
- return tx.CreateSymbolic("refs/heads/worktree-target", "worktrees/wt1/HEAD")
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tt.queue(tx)
- if (err != nil) != tt.wantErr {
- t.Fatalf("queue err=%v, wantErr=%v", err, tt.wantErr)
- }
-
- _ = tx.Abort()
- })
- }
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(final symbolic): %v", err)
- }
-
- err = tx.CreateSymbolic("refs/heads/top-level", "ORIG_HEAD")
- if err != nil {
- t.Fatalf("CreateSymbolic(top-level): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(CreateSymbolic top-level): %v", err)
- }
-
- got, err := store.Resolve("refs/heads/top-level")
- if err != nil {
- t.Fatalf("Resolve(top-level): %v", err)
- }
-
- sym, ok := got.(ref.Symbolic)
- if !ok {
- t.Fatalf("Resolve(top-level) type = %T, want ref.Symbolic", got)
- }
-
- if sym.Target != "ORIG_HEAD" {
- t.Fatalf("top-level target = %q, want %q", sym.Target, "ORIG_HEAD")
- }
- })
-}
diff --git a/ref/store/files/transaction_pseudoref_test.go b/ref/store/files/transaction_pseudoref_test.go
deleted file mode 100644
index ea57e92f..00000000
--- a/ref/store/files/transaction_pseudoref_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package files_test
-
-import (
- "errors"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesTransactionPseudorefLifecycle(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, aID := testRepo.MakeCommit(t, "A")
- _, _, bID := testRepo.MakeCommit(t, "B")
- _, _, cID := testRepo.MakeCommit(t, "C")
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(create): %v", err)
- }
-
- err = tx.Create("PSEUDOREF", aID)
- if err != nil {
- t.Fatalf("Create(PSEUDOREF): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(create PSEUDOREF): %v", err)
- }
-
- got, err := store.ResolveToDetached("PSEUDOREF")
- if err != nil {
- t.Fatalf("ResolveToDetached(PSEUDOREF): %v", err)
- }
-
- if got.ID != aID {
- t.Fatalf("PSEUDOREF after create = %s, want %s", got.ID, aID)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(update): %v", err)
- }
-
- err = tx.Update("PSEUDOREF", bID, aID)
- if err != nil {
- t.Fatalf("Update(PSEUDOREF): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(update PSEUDOREF): %v", err)
- }
-
- got, err = store.ResolveToDetached("PSEUDOREF")
- if err != nil {
- t.Fatalf("ResolveToDetached(PSEUDOREF) after update: %v", err)
- }
-
- if got.ID != bID {
- t.Fatalf("PSEUDOREF after update = %s, want %s", got.ID, bID)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(stale update): %v", err)
- }
-
- err = tx.Update("PSEUDOREF", cID, aID)
- if err != nil {
- t.Fatalf("queue stale update: %v", err)
- }
-
- err = tx.Commit()
- if err == nil {
- t.Fatal("stale pseudoref update unexpectedly succeeded")
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete): %v", err)
- }
-
- err = tx.Delete("PSEUDOREF", bID)
- if err != nil {
- t.Fatalf("Delete(PSEUDOREF): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(delete PSEUDOREF): %v", err)
- }
-
- _, err = store.Resolve("PSEUDOREF")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(PSEUDOREF after delete) err=%v", err)
- }
- })
-}
diff --git a/ref/store/files/transaction_queue.go b/ref/store/files/transaction_queue.go
deleted file mode 100644
index aa2004c3..00000000
--- a/ref/store/files/transaction_queue.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package files
-
-func (tx *Transaction) queue(op queuedUpdate) error {
- err := (&refUpdateExecutor{store: tx.store}).validateQueuedUpdate(op)
- if err != nil {
- return err
- }
-
- tx.ops = append(tx.ops, op)
-
- return nil
-}
diff --git a/ref/store/files/transaction_queue_ops.go b/ref/store/files/transaction_queue_ops.go
deleted file mode 100644
index 63f48254..00000000
--- a/ref/store/files/transaction_queue_ops.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package files
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Create queues a detached reference creation.
-func (tx *Transaction) Create(name string, newID objectid.ObjectID) error {
- return tx.queue(queuedUpdate{name: name, kind: updateCreate, newID: newID})
-}
-
-// Update queues a detached reference update.
-func (tx *Transaction) Update(name string, newID, oldID objectid.ObjectID) error {
- return tx.queue(queuedUpdate{name: name, kind: updateReplace, newID: newID, oldID: oldID})
-}
-
-// Delete queues a detached reference deletion.
-func (tx *Transaction) Delete(name string, oldID objectid.ObjectID) error {
- return tx.queue(queuedUpdate{name: name, kind: updateDelete, oldID: oldID})
-}
-
-// Verify queues a detached reference verification.
-func (tx *Transaction) Verify(name string, oldID objectid.ObjectID) error {
- return tx.queue(queuedUpdate{name: name, kind: updateVerify, oldID: oldID})
-}
-
-// CreateSymbolic queues a symbolic reference creation.
-func (tx *Transaction) CreateSymbolic(name, newTarget string) error {
- return tx.queue(queuedUpdate{name: name, kind: updateCreateSymbolic, newTarget: newTarget})
-}
-
-// UpdateSymbolic queues a symbolic reference update.
-func (tx *Transaction) UpdateSymbolic(name, newTarget, oldTarget string) error {
- return tx.queue(queuedUpdate{name: name, kind: updateReplaceSymbolic, newTarget: newTarget, oldTarget: oldTarget})
-}
-
-// DeleteSymbolic queues a symbolic reference deletion.
-func (tx *Transaction) DeleteSymbolic(name, oldTarget string) error {
- return tx.queue(queuedUpdate{name: name, kind: updateDeleteSymbolic, oldTarget: oldTarget})
-}
-
-// VerifySymbolic queues a symbolic reference verification.
-func (tx *Transaction) VerifySymbolic(name, oldTarget string) error {
- return tx.queue(queuedUpdate{name: name, kind: updateVerifySymbolic, oldTarget: oldTarget})
-}
diff --git a/ref/store/files/transaction_symbolic_test.go b/ref/store/files/transaction_symbolic_test.go
deleted file mode 100644
index 360686d6..00000000
--- a/ref/store/files/transaction_symbolic_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package files_test
-
-import (
- "errors"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesTransactionDirectSymbolicDeletes(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, mainID := testRepo.MakeCommit(t, "main")
- testRepo.UpdateRef(t, "refs/heads/main", mainID)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(create symref): %v", err)
- }
-
- err = tx.CreateSymbolic("SYMREF", "refs/heads/main")
- if err != nil {
- t.Fatalf("CreateSymbolic(SYMREF): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(CreateSymbolic SYMREF): %v", err)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete symref): %v", err)
- }
-
- err = tx.DeleteSymbolic("SYMREF", "refs/heads/main")
- if err != nil {
- t.Fatalf("DeleteSymbolic(SYMREF): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(DeleteSymbolic SYMREF): %v", err)
- }
-
- _, err = store.Resolve("SYMREF")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(SYMREF after delete) err=%v", err)
- }
-
- got, err := store.ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if got.ID != mainID {
- t.Fatalf("main after DeleteSymbolic = %s, want %s", got.ID, mainID)
- }
- })
-}
-
-func TestFilesTransactionSelfAndDanglingSymrefs(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, mainID := testRepo.MakeCommit(t, "main")
- testRepo.UpdateRef(t, "refs/heads/main", mainID)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(create self): %v", err)
- }
-
- err = tx.CreateSymbolic("refs/heads/self", "refs/heads/self")
- if err != nil {
- t.Fatalf("CreateSymbolic(self): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(CreateSymbolic self): %v", err)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete logical self): %v", err)
- }
-
- err = tx.Delete("refs/heads/self", mainID)
- if err == nil {
- err = tx.Commit()
- } else {
- _ = tx.Abort()
- }
-
- if err == nil {
- t.Fatal("Delete(self) unexpectedly succeeded")
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete symbolic self): %v", err)
- }
-
- err = tx.DeleteSymbolic("refs/heads/self", "refs/heads/self")
- if err != nil {
- t.Fatalf("DeleteSymbolic(self): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(DeleteSymbolic self): %v", err)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(create dangling): %v", err)
- }
-
- err = tx.CreateSymbolic("refs/heads/dangling", "refs/heads/missing")
- if err != nil {
- t.Fatalf("CreateSymbolic(dangling): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(CreateSymbolic dangling): %v", err)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(delete dangling): %v", err)
- }
-
- err = tx.DeleteSymbolic("refs/heads/dangling", "refs/heads/missing")
- if err != nil {
- t.Fatalf("DeleteSymbolic(dangling): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(DeleteSymbolic dangling): %v", err)
- }
- })
-}
diff --git a/ref/store/files/transaction_update_test.go b/ref/store/files/transaction_update_test.go
deleted file mode 100644
index a29d586e..00000000
--- a/ref/store/files/transaction_update_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-package files_test
-
-import (
- "errors"
- "strings"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesTransactionPackedUpdateCreatesLooseOverride(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, oldID := testRepo.MakeCommit(t, "old packed")
- _, _, newID := testRepo.MakeCommit(t, "new loose")
- testRepo.UpdateRef(t, "refs/heads/main", oldID)
- testRepo.PackRefs(t, "--all", "--prune")
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tx.Update("refs/heads/main", newID, oldID)
- if err != nil {
- t.Fatalf("Update queue: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit: %v", err)
- }
-
- got, err := store.ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if got.ID != newID {
- t.Fatalf("ResolveToDetached(main) = %s, want %s", got.ID, newID)
- }
-
- packedRefs := string(testRepo.ReadFile(t, "packed-refs"))
- if !strings.Contains(packedRefs, oldID.String()+" refs/heads/main\n") {
- t.Fatalf("packed-refs lost old packed main entry:\n%s", packedRefs)
- }
-
- looseMain := string(testRepo.ReadFile(t, "refs/heads/main"))
- if strings.TrimSpace(looseMain) != newID.String() {
- t.Fatalf("loose refs/heads/main = %q, want %q", strings.TrimSpace(looseMain), newID.String())
- }
- })
-}
-
-func TestFilesTransactionDeletesPackedAndLooseRefs(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, packedOnlyID := testRepo.MakeCommit(t, "packed only")
- _, _, bothID := testRepo.MakeCommit(t, "both")
- testRepo.UpdateRef(t, "refs/heads/packed", packedOnlyID)
- testRepo.UpdateRef(t, "refs/heads/both", bothID)
- testRepo.PackRefs(t, "--all", "--prune")
- testRepo.UpdateRef(t, "refs/heads/both", bothID)
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction: %v", err)
- }
-
- err = tx.Delete("refs/heads/packed", packedOnlyID)
- if err != nil {
- t.Fatalf("Delete(packed): %v", err)
- }
-
- err = tx.Delete("refs/heads/both", bothID)
- if err != nil {
- t.Fatalf("Delete(both): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(delete): %v", err)
- }
-
- _, err = store.Resolve("refs/heads/packed")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(packed after delete) error = %v", err)
- }
-
- _, err = store.Resolve("refs/heads/both")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(both after delete) error = %v", err)
- }
-
- packedRefs := string(testRepo.ReadFile(t, "packed-refs"))
- if strings.Contains(packedRefs, "refs/heads/packed\n") || strings.Contains(packedRefs, "refs/heads/both\n") {
- t.Fatalf("packed-refs still contains deleted refs:\n%s", packedRefs)
- }
- })
-}
-
-func TestFilesTransactionDerefAndDirectSymbolic(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, firstID := testRepo.MakeCommit(t, "first")
- _, _, secondID := testRepo.MakeCommit(t, "second")
- testRepo.UpdateRef(t, "refs/heads/main", firstID)
- testRepo.SymbolicRef(t, "HEAD", "refs/heads/main")
-
- store := openFilesStore(t, testRepo, algo)
-
- tx, err := store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(update): %v", err)
- }
-
- err = tx.Update("HEAD", secondID, firstID)
- if err != nil {
- t.Fatalf("Update(HEAD): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(update HEAD): %v", err)
- }
-
- mainRef, err := store.ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if mainRef.ID != secondID {
- t.Fatalf("main after Update(HEAD) = %s, want %s", mainRef.ID, secondID)
- }
-
- tx, err = store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(update symbolic): %v", err)
- }
-
- err = tx.UpdateSymbolic("HEAD", "refs/heads/next", "refs/heads/main")
- if err != nil {
- t.Fatalf("UpdateSymbolic(HEAD): %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- t.Fatalf("Commit(update symbolic HEAD): %v", err)
- }
-
- headRef, err := store.Resolve("HEAD")
- if err != nil {
- t.Fatalf("Resolve(HEAD): %v", err)
- }
-
- headSym, ok := headRef.(ref.Symbolic)
- if !ok {
- t.Fatalf("Resolve(HEAD) type = %T, want ref.Symbolic", headRef)
- }
-
- if headSym.Target != "refs/heads/next" {
- t.Fatalf("HEAD target = %q, want %q", headSym.Target, "refs/heads/next")
- }
- })
-}
diff --git a/ref/store/files/trim.go b/ref/store/files/trim.go
deleted file mode 100644
index 69a851dc..00000000
--- a/ref/store/files/trim.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package files
-
-func isRefWhitespace(r rune) bool {
- switch r {
- case ' ', '\t', '\n', '\r', '\v', '\f':
- return true
- default:
- return false
- }
-}
diff --git a/ref/store/files/update_cleanup.go b/ref/store/files/update_cleanup.go
deleted file mode 100644
index 5df2d967..00000000
--- a/ref/store/files/update_cleanup.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
- "slices"
-)
-
-func (executor *refUpdateExecutor) cleanupPreparedUpdates(prepared []preparedUpdate) error {
- var firstErr error
-
- lockNames := make([]string, 0, len(prepared)+1)
- for _, item := range prepared {
- lockNames = append(lockNames, updateTargetKey(item.target.loc))
- }
-
- lockNames = append(lockNames, updateTargetKey(refPath{root: rootCommon, path: "packed-refs"}))
- slices.Sort(lockNames)
- lockNames = slices.Compact(lockNames)
-
- for _, lockKey := range lockNames {
- lockPath := refPathFromKey(lockKey)
- lockName := lockPath.path + ".lock"
- root := executor.store.rootFor(lockPath.root)
-
- err := root.Remove(lockName)
- if err == nil || errors.Is(err, os.ErrNotExist) {
- executor.tryRemoveEmptyParentPaths(lockPath.root, lockName)
-
- continue
- }
-
- if firstErr == nil {
- firstErr = err
- }
- }
-
- return firstErr
-}
diff --git a/ref/store/files/update_cleanup_parents.go b/ref/store/files/update_cleanup_parents.go
deleted file mode 100644
index 5a994dcd..00000000
--- a/ref/store/files/update_cleanup_parents.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
- "path"
-)
-
-func (executor *refUpdateExecutor) tryRemoveEmptyParents(name string) {
- loc := executor.store.loosePath(name)
- executor.tryRemoveEmptyParentPaths(loc.root, loc.path)
-}
-
-func (executor *refUpdateExecutor) tryRemoveEmptyParentPaths(kind rootKind, name string) {
- root := executor.store.rootFor(kind)
- dir := path.Dir(name)
-
- for dir != "." && dir != "/" {
- err := root.Remove(dir)
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return
- }
-
- return
- }
-
- dir = path.Dir(dir)
- }
-}
diff --git a/ref/store/files/update_commit.go b/ref/store/files/update_commit.go
deleted file mode 100644
index 3d39e990..00000000
--- a/ref/store/files/update_commit.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package files
-
-func (executor *refUpdateExecutor) commitPreparedUpdates(prepared []preparedUpdate) (err error) {
- defer func() {
- _ = executor.cleanupPreparedUpdates(prepared)
- }()
-
- for _, item := range prepared {
- if item.op.kind == updateDelete || item.op.kind == updateDeleteSymbolic || item.op.kind == updateVerify || item.op.kind == updateVerifySymbolic {
- continue
- }
-
- err = executor.writePreparedLooseUpdate(item)
- if err != nil {
- return wrapUpdateError(item.op.name, err)
- }
- }
-
- err = executor.applyPackedRefDeletes(prepared)
- if err != nil {
- return err
- }
-
- return executor.removeDeletedLooseRefs(prepared)
-}
diff --git a/ref/store/files/update_commit_delete.go b/ref/store/files/update_commit_delete.go
deleted file mode 100644
index 47a600fb..00000000
--- a/ref/store/files/update_commit_delete.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
-)
-
-func (executor *refUpdateExecutor) removeDeletedLooseRefs(prepared []preparedUpdate) error {
- for _, item := range prepared {
- switch item.op.kind {
- case updateDelete, updateDeleteSymbolic:
- if item.target.ref.isLoose {
- err := executor.store.rootFor(item.target.loc.root).Remove(item.target.loc.path)
- if err != nil && !errors.Is(err, os.ErrNotExist) {
- return wrapUpdateError(item.op.name, err)
- }
-
- executor.tryRemoveEmptyParents(item.target.name)
- }
- case updateCreate, updateReplace, updateVerify, updateCreateSymbolic, updateReplaceSymbolic, updateVerifySymbolic:
- }
- }
-
- return nil
-}
diff --git a/ref/store/files/update_dir_tree.go b/ref/store/files/update_dir_tree.go
deleted file mode 100644
index 51fb5cfb..00000000
--- a/ref/store/files/update_dir_tree.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package files
-
-import (
- "errors"
- "fmt"
- "os"
- "path"
-)
-
-func (executor *refUpdateExecutor) removeEmptyDirTree(name refPath) error {
- root := executor.store.rootFor(name.root)
-
- info, err := root.Stat(name.path)
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return nil
- }
-
- return err
- }
-
- if !info.IsDir() {
- return nil
- }
-
- return executor.removeEmptyDirTreeRecursive(name)
-}
-
-func (executor *refUpdateExecutor) removeEmptyDirTreeRecursive(name refPath) error {
- root := executor.store.rootFor(name.root)
-
- dir, err := root.Open(name.path)
- if err != nil {
- return err
- }
-
- entries, err := dir.ReadDir(-1)
- _ = dir.Close()
-
- if err != nil {
- return err
- }
-
- for _, entry := range entries {
- if !entry.IsDir() {
- return fmt.Errorf("refstore/files: non-empty directory blocks reference %q", name.path)
- }
-
- err = executor.removeEmptyDirTreeRecursive(refPath{
- root: name.root,
- path: path.Join(name.path, entry.Name()),
- })
- if err != nil {
- return err
- }
- }
-
- return root.Remove(name.path)
-}
diff --git a/ref/store/files/update_direct_read.go b/ref/store/files/update_direct_read.go
deleted file mode 100644
index 50e15026..00000000
--- a/ref/store/files/update_direct_read.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package files
-
-import (
- "errors"
- "fmt"
-
- "codeberg.org/lindenii/furgit/ref"
- "codeberg.org/lindenii/furgit/ref/name"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (executor *refUpdateExecutor) directRead(name string) (directRefState, error) {
- loc := executor.store.loosePath(name)
- hasPacked := false
-
- if loc.root == rootCommon && refname.ParseWorktree(name).Type == refname.WorktreeShared {
- packed, packedErr := executor.store.readPackedRefs()
- if packedErr != nil {
- return directRefState{}, packedErr
- }
-
- _, hasPacked = packed.byName[name]
- }
-
- loose, err := executor.store.readLooseRef(name)
- if err == nil {
- switch loose := loose.(type) {
- case ref.Detached:
- return directRefState{
- kind: directDetached,
- name: name,
- id: loose.ID,
- isLoose: true,
- isPacked: hasPacked,
- }, nil
- case ref.Symbolic:
- return directRefState{
- kind: directSymbolic,
- name: name,
- target: loose.Target,
- isLoose: true,
- isPacked: hasPacked,
- }, nil
- default:
- return directRefState{}, fmt.Errorf("refstore/files: unsupported reference type %T", loose)
- }
- }
-
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- info, statErr := executor.store.rootFor(loc.root).Stat(loc.path)
- if statErr != nil || !info.IsDir() {
- return directRefState{}, err
- }
- }
-
- if hasPacked {
- packed, packedErr := executor.store.readPackedRefs()
- if packedErr != nil {
- return directRefState{}, packedErr
- }
-
- detached := packed.byName[name]
-
- return directRefState{
- kind: directDetached,
- name: name,
- id: detached.ID,
- isPacked: true,
- }, nil
- }
-
- return directRefState{
- kind: directMissing,
- name: name,
- }, nil
-}
diff --git a/ref/store/files/update_direct_ref.go b/ref/store/files/update_direct_ref.go
deleted file mode 100644
index 3b429be0..00000000
--- a/ref/store/files/update_direct_ref.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package files
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-type directRefKind uint8
-
-const (
- directMissing directRefKind = iota
- directDetached
- directSymbolic
-)
-
-type directRefState struct {
- kind directRefKind
- name string
- id objectid.ObjectID
- target string
- isLoose bool
- isPacked bool
-}
diff --git a/ref/store/files/update_error.go b/ref/store/files/update_error.go
deleted file mode 100644
index d8841d44..00000000
--- a/ref/store/files/update_error.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package files
-
-import "fmt"
-
-type updateContextError struct {
- name string
- err error
-}
-
-func (err *updateContextError) Error() string {
- return fmt.Sprintf("refstore/files: update %q: %v", err.name, err.err)
-}
-
-func (err *updateContextError) Unwrap() error {
- if err == nil {
- return nil
- }
-
- return err.err
-}
-
-func wrapUpdateError(name string, err error) error {
- if err == nil || name == "" {
- return err
- }
-
- return &updateContextError{name: name, err: err}
-}
diff --git a/ref/store/files/update_executor.go b/ref/store/files/update_executor.go
deleted file mode 100644
index 749f7061..00000000
--- a/ref/store/files/update_executor.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package files
-
-type refUpdateExecutor struct {
- store *Store
-}
diff --git a/ref/store/files/update_kind.go b/ref/store/files/update_kind.go
deleted file mode 100644
index f04719db..00000000
--- a/ref/store/files/update_kind.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package files
-
-type updateKind uint8
-
-const (
- updateCreate updateKind = iota
- updateReplace
- updateDelete
- updateVerify
- updateCreateSymbolic
- updateReplaceSymbolic
- updateDeleteSymbolic
- updateVerifySymbolic
-)
diff --git a/ref/store/files/update_lock.go b/ref/store/files/update_lock.go
deleted file mode 100644
index 1ce9adbb..00000000
--- a/ref/store/files/update_lock.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package files
-
-import (
- "os"
- "path"
-)
-
-func (executor *refUpdateExecutor) createUpdateLock(name refPath) error {
- root := executor.store.rootFor(name.root)
- dir := path.Dir(name.path)
-
- if dir != "." {
- err := root.MkdirAll(dir, 0o755)
- if err != nil {
- return err
- }
- }
-
- file, err := root.OpenFile(name.path+".lock", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
- if err != nil {
- return err
- }
-
- return file.Close()
-}
diff --git a/ref/store/files/update_lock_packed.go b/ref/store/files/update_lock_packed.go
deleted file mode 100644
index f74a4f5e..00000000
--- a/ref/store/files/update_lock_packed.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
- "time"
-)
-
-func (executor *refUpdateExecutor) createPackedRefsLock(timeout time.Duration) error {
- const (
- initialBackoffMs = 1
- backoffMaxMultiplier = 1000
- )
-
- deadline := time.Now().Add(timeout)
- multiplier := 1
- n := 1
-
- for {
- file, err := executor.store.commonRoot.OpenFile("packed-refs.lock", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
- if err == nil {
- return file.Close()
- }
-
- if !errors.Is(err, os.ErrExist) {
- return err
- }
-
- if timeout == 0 || (timeout > 0 && time.Now().After(deadline)) {
- return err
- }
-
- backoffMs := multiplier * initialBackoffMs
- waitMs := (750 + executor.store.lockRand.Intn(500)) * backoffMs / 1000
- time.Sleep(time.Duration(waitMs) * time.Millisecond)
-
- multiplier += 2*n + 1
- if multiplier > backoffMaxMultiplier {
- multiplier = backoffMaxMultiplier
- } else {
- n++
- }
- }
-}
diff --git a/ref/store/files/update_operation_prepared.go b/ref/store/files/update_operation_prepared.go
deleted file mode 100644
index c50fea4e..00000000
--- a/ref/store/files/update_operation_prepared.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package files
-
-type preparedUpdate struct {
- op queuedUpdate
- target resolvedUpdateTarget
-}
diff --git a/ref/store/files/update_operation_queue.go b/ref/store/files/update_operation_queue.go
deleted file mode 100644
index ef7ced2f..00000000
--- a/ref/store/files/update_operation_queue.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package files
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-type queuedUpdate struct {
- name string
- kind updateKind
- newID objectid.ObjectID
- oldID objectid.ObjectID
- newTarget string
- oldTarget string
-}
diff --git a/ref/store/files/update_path.go b/ref/store/files/update_path.go
deleted file mode 100644
index 2bd42535..00000000
--- a/ref/store/files/update_path.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package files
-
-import (
- "fmt"
- "strings"
-)
-
-type refPath struct {
- root rootKind
- path string
-}
-
-func updateTargetKey(name refPath) string {
- return fmt.Sprintf("%d:%s", name.root, name.path)
-}
-
-func refPathFromKey(key string) refPath {
- rootValue, pathValue, ok := strings.Cut(key, ":")
- if !ok || rootValue == "" {
- return refPath{root: rootCommon, path: key}
- }
-
- if rootValue == "0" {
- return refPath{root: rootGit, path: pathValue}
- }
-
- return refPath{root: rootCommon, path: pathValue}
-}
diff --git a/ref/store/files/update_prepare.go b/ref/store/files/update_prepare.go
deleted file mode 100644
index 035c0bc2..00000000
--- a/ref/store/files/update_prepare.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package files
-
-func (executor *refUpdateExecutor) prepareUpdates(ops []queuedUpdate) (prepared []preparedUpdate, err error) {
- defer func() {
- if err != nil {
- _ = executor.cleanupPreparedUpdates(prepared)
- }
- }()
-
- prepared, err = executor.resolvePreparedUpdates(ops)
- if err != nil {
- return prepared, err
- }
-
- deleted, written := collectPreparedWrites(prepared)
-
- existing, err := executor.collectVisibleNames()
- if err != nil {
- return prepared, err
- }
-
- for _, name := range written {
- err = verifyRefnameAvailable(name, existing, written, deleted)
- if err != nil {
- return prepared, err
- }
- }
-
- err = executor.prepareUpdateLocks(prepared)
- if err != nil {
- return prepared, err
- }
-
- hasDeletes := len(deleted) > 0
- if hasDeletes {
- err = executor.createPackedRefsLock(executor.store.packedRefsTimeout)
- if err != nil {
- return prepared, err
- }
- }
-
- err = executor.verifyPreparedUpdates(prepared)
- if err != nil {
- return prepared, err
- }
-
- return prepared, nil
-}
diff --git a/ref/store/files/update_prepare_lock.go b/ref/store/files/update_prepare_lock.go
deleted file mode 100644
index 67db9628..00000000
--- a/ref/store/files/update_prepare_lock.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package files
-
-import "slices"
-
-func (executor *refUpdateExecutor) prepareUpdateLocks(prepared []preparedUpdate) error {
- lockNames := make([]string, 0, len(prepared))
- for _, item := range prepared {
- lockNames = append(lockNames, updateTargetKey(item.target.loc))
- }
-
- slices.Sort(lockNames)
-
- for _, lockKey := range lockNames {
- lockPath := refPathFromKey(lockKey)
-
- err := executor.createUpdateLock(lockPath)
- if err != nil {
- for _, item := range prepared {
- if updateTargetKey(item.target.loc) == lockKey {
- return wrapUpdateError(item.op.name, err)
- }
- }
-
- return err
- }
- }
-
- return nil
-}
diff --git a/ref/store/files/update_prepare_resolve.go b/ref/store/files/update_prepare_resolve.go
deleted file mode 100644
index 19d209b0..00000000
--- a/ref/store/files/update_prepare_resolve.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package files
-
-import refstore "codeberg.org/lindenii/furgit/ref/store"
-
-func (executor *refUpdateExecutor) resolvePreparedUpdates(ops []queuedUpdate) ([]preparedUpdate, error) {
- prepared := make([]preparedUpdate, 0, len(ops))
- targets := make(map[string]struct{}, len(ops))
-
- for _, op := range ops {
- target, err := executor.resolveQueuedUpdateTarget(op)
- if err != nil {
- return prepared, err
- }
-
- targetKey := updateTargetKey(target.loc)
- if _, exists := targets[targetKey]; exists {
- return prepared, wrapUpdateError(op.name, &refstore.DuplicateUpdateError{})
- }
-
- targets[targetKey] = struct{}{}
-
- prepared = append(prepared, preparedUpdate{op: op, target: target})
- }
-
- return prepared, nil
-}
-
-func collectPreparedWrites(prepared []preparedUpdate) (deleted map[string]struct{}, written []string) {
- deleted = make(map[string]struct{})
- written = make([]string, 0, len(prepared))
-
- for _, item := range prepared {
- switch item.op.kind {
- case updateDelete, updateDeleteSymbolic:
- deleted[item.target.name] = struct{}{}
- case updateCreate, updateReplace, updateCreateSymbolic, updateReplaceSymbolic:
- written = append(written, item.target.name)
- case updateVerify, updateVerifySymbolic:
- }
- }
-
- return deleted, written
-}
diff --git a/ref/store/files/update_prepare_verify.go b/ref/store/files/update_prepare_verify.go
deleted file mode 100644
index dcd14945..00000000
--- a/ref/store/files/update_prepare_verify.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package files
-
-func (executor *refUpdateExecutor) verifyPreparedUpdates(prepared []preparedUpdate) error {
- for i := range prepared {
- item := &prepared[i]
-
- refState, err := executor.directRead(item.target.name)
- if err != nil {
- return wrapUpdateError(item.op.name, err)
- }
-
- item.target.ref = refState
-
- err = executor.verifyPreparedUpdateCurrent(*item)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/ref/store/files/update_resolve_target.go b/ref/store/files/update_resolve_target.go
deleted file mode 100644
index 7cfb9aa1..00000000
--- a/ref/store/files/update_resolve_target.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package files
-
-import "fmt"
-
-func (executor *refUpdateExecutor) resolveQueuedUpdateTarget(op queuedUpdate) (resolvedUpdateTarget, error) {
- switch op.kind {
- case updateCreate:
- return executor.resolveOrdinaryTarget(op.name, true)
- case updateReplace, updateDelete, updateVerify:
- return executor.resolveOrdinaryTarget(op.name, false)
- case updateCreateSymbolic, updateReplaceSymbolic, updateDeleteSymbolic, updateVerifySymbolic:
- refState, err := executor.directRead(op.name)
- if err != nil {
- return resolvedUpdateTarget{}, err
- }
-
- return resolvedUpdateTarget{name: op.name, loc: executor.store.loosePath(op.name), ref: refState}, nil
- default:
- return resolvedUpdateTarget{}, fmt.Errorf("refstore/files: unsupported update operation %d", op.kind)
- }
-}
diff --git a/ref/store/files/update_resolve_target_ordinary.go b/ref/store/files/update_resolve_target_ordinary.go
deleted file mode 100644
index cf8e1978..00000000
--- a/ref/store/files/update_resolve_target_ordinary.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package files
-
-import (
- "fmt"
- "strings"
-
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (executor *refUpdateExecutor) resolveOrdinaryTarget(name string, allowMissing bool) (resolvedUpdateTarget, error) {
- cur := name
- seen := make(map[string]struct{})
-
- for {
- if _, ok := seen[cur]; ok {
- return resolvedUpdateTarget{}, fmt.Errorf("refstore/files: symbolic reference cycle at %q", cur)
- }
-
- seen[cur] = struct{}{}
-
- refState, err := executor.directRead(cur)
- if err != nil {
- return resolvedUpdateTarget{}, err
- }
-
- switch refState.kind {
- case directMissing:
- if !allowMissing {
- return resolvedUpdateTarget{}, wrapUpdateError(name, refstore.ErrReferenceNotFound)
- }
-
- return resolvedUpdateTarget{name: cur, loc: executor.store.loosePath(cur), ref: refState}, nil
- case directDetached:
- return resolvedUpdateTarget{name: cur, loc: executor.store.loosePath(cur), ref: refState}, nil
- case directSymbolic:
- target := strings.TrimSpace(refState.target)
- if target == "" {
- return resolvedUpdateTarget{}, wrapUpdateError(name, &refstore.InvalidValueError{
- Err: fmt.Errorf("symbolic reference has empty target"),
- })
- }
-
- cur = target
- default:
- return resolvedUpdateTarget{}, fmt.Errorf("refstore/files: unsupported direct reference state %d", refState.kind)
- }
- }
-}
diff --git a/ref/store/files/update_target_resolved.go b/ref/store/files/update_target_resolved.go
deleted file mode 100644
index c29e5938..00000000
--- a/ref/store/files/update_target_resolved.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package files
-
-type resolvedUpdateTarget struct {
- name string
- loc refPath
- ref directRefState
-}
diff --git a/ref/store/files/update_validate.go b/ref/store/files/update_validate.go
deleted file mode 100644
index ac3429aa..00000000
--- a/ref/store/files/update_validate.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package files
-
-import (
- "fmt"
- "strings"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/ref/name"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (executor *refUpdateExecutor) validateQueuedUpdate(op queuedUpdate) error {
- if op.name == "" {
- return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: fmt.Errorf("empty reference name")})
- }
-
- switch op.kind {
- case updateCreate, updateReplace:
- err := refname.ValidateUpdateName(op.name, true)
- if err != nil {
- return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})
- }
-
- if op.newID.Algorithm().Size() == 0 {
- return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: objectid.ErrInvalidAlgorithm})
- }
- case updateDelete, updateVerify:
- err := refname.ValidateUpdateName(op.name, false)
- if err != nil {
- return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})
- }
-
- if op.oldID.Algorithm().Size() == 0 {
- return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: objectid.ErrInvalidAlgorithm})
- }
- case updateCreateSymbolic, updateReplaceSymbolic:
- err := refname.ValidateUpdateName(op.name, true)
- if err != nil {
- return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})
- }
-
- if strings.TrimSpace(op.newTarget) == "" {
- return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: fmt.Errorf("empty symbolic target")})
- }
-
- err = refname.ValidateSymbolicTarget(op.name, strings.TrimSpace(op.newTarget))
- if err != nil {
- return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: err})
- }
- case updateDeleteSymbolic, updateVerifySymbolic:
- err := refname.ValidateUpdateName(op.name, false)
- if err != nil {
- return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})
- }
- default:
- return fmt.Errorf("refstore/files: unsupported update operation %d", op.kind)
- }
-
- if op.kind == updateReplaceSymbolic || op.kind == updateDeleteSymbolic || op.kind == updateVerifySymbolic {
- if strings.TrimSpace(op.oldTarget) == "" {
- return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: fmt.Errorf("empty symbolic old target")})
- }
- }
-
- return nil
-}
diff --git a/ref/store/files/update_verify_current.go b/ref/store/files/update_verify_current.go
deleted file mode 100644
index 51ed1b42..00000000
--- a/ref/store/files/update_verify_current.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package files
-
-import (
- "strings"
-
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (executor *refUpdateExecutor) verifyPreparedUpdateCurrent(item preparedUpdate) error {
- switch item.op.kind {
- case updateCreate:
- if item.target.ref.kind != directMissing {
- return wrapUpdateError(item.op.name, &refstore.CreateExistsError{})
- }
-
- return nil
- case updateReplace, updateDelete, updateVerify:
- if item.target.ref.kind == directMissing {
- return wrapUpdateError(item.op.name, refstore.ErrReferenceNotFound)
- }
-
- if item.target.ref.kind != directDetached {
- return wrapUpdateError(item.op.name, &refstore.ExpectedDetachedError{})
- }
-
- if item.target.ref.id != item.op.oldID {
- return wrapUpdateError(item.op.name, &refstore.IncorrectOldValueError{
- Actual: item.target.ref.id.String(),
- Expected: item.op.oldID.String(),
- })
- }
-
- return nil
- case updateCreateSymbolic:
- if item.target.ref.kind != directMissing {
- return wrapUpdateError(item.op.name, &refstore.CreateExistsError{})
- }
-
- return nil
- case updateReplaceSymbolic, updateDeleteSymbolic, updateVerifySymbolic:
- if item.target.ref.kind == directMissing {
- return wrapUpdateError(item.op.name, refstore.ErrReferenceNotFound)
- }
-
- if item.target.ref.kind != directSymbolic {
- return wrapUpdateError(item.op.name, &refstore.ExpectedSymbolicError{})
- }
-
- if strings.TrimSpace(item.target.ref.target) != strings.TrimSpace(item.op.oldTarget) {
- return wrapUpdateError(item.op.name, &refstore.IncorrectOldValueError{
- Actual: strings.TrimSpace(item.target.ref.target),
- Expected: strings.TrimSpace(item.op.oldTarget),
- })
- }
-
- return nil
- }
-
- return nil
-}
diff --git a/ref/store/files/update_verify_refnames.go b/ref/store/files/update_verify_refnames.go
deleted file mode 100644
index 8bc34a62..00000000
--- a/ref/store/files/update_verify_refnames.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package files
-
-import (
- "strings"
-
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func verifyRefnameAvailable(name string, existing map[string]struct{}, writes []string, deleted map[string]struct{}) error {
- for existingName := range existing {
- if existingName == name {
- continue
- }
-
- if _, skip := deleted[existingName]; skip {
- continue
- }
-
- if refnamesConflict(name, existingName) {
- return wrapUpdateError(name, &refstore.NameConflictError{Other: existingName})
- }
- }
-
- for _, other := range writes {
- if other == name {
- continue
- }
-
- if refnamesConflict(name, other) {
- return wrapUpdateError(name, &refstore.NameConflictError{Other: other})
- }
- }
-
- return nil
-}
-
-func refnamesConflict(left, right string) bool {
- return left == right ||
- strings.HasPrefix(left, right+"/") ||
- strings.HasPrefix(right, left+"/")
-}
diff --git a/ref/store/files/update_visible_names.go b/ref/store/files/update_visible_names.go
deleted file mode 100644
index f5792f93..00000000
--- a/ref/store/files/update_visible_names.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package files
-
-func (executor *refUpdateExecutor) collectVisibleNames() (map[string]struct{}, error) {
- names := make(map[string]struct{})
-
- looseNames, err := executor.store.collectLooseRefNames()
- if err != nil {
- return nil, err
- }
-
- for _, name := range looseNames {
- names[name] = struct{}{}
- }
-
- packed, err := executor.store.readPackedRefs()
- if err != nil {
- return nil, err
- }
-
- for name := range packed.byName {
- if _, exists := names[name]; exists {
- continue
- }
-
- names[name] = struct{}{}
- }
-
- return names, nil
-}
diff --git a/ref/store/files/update_write_loose.go b/ref/store/files/update_write_loose.go
deleted file mode 100644
index 212be9a8..00000000
--- a/ref/store/files/update_write_loose.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package files
-
-import (
- "fmt"
- "os"
- "path"
- "strings"
-)
-
-func (executor *refUpdateExecutor) writePreparedLooseUpdate(item preparedUpdate) error {
- root := executor.store.rootFor(item.target.loc.root)
- lockName := item.target.loc.path + ".lock"
-
- lock, err := root.OpenFile(lockName, os.O_WRONLY|os.O_TRUNC, 0o644)
- if err != nil {
- return err
- }
-
- var content string
-
- switch item.op.kind {
- case updateCreate, updateReplace:
- content = item.op.newID.String() + "\n"
- case updateCreateSymbolic, updateReplaceSymbolic:
- content = "ref: " + strings.TrimSpace(item.op.newTarget) + "\n"
- case updateDelete, updateVerify, updateDeleteSymbolic, updateVerifySymbolic:
- default:
- _ = lock.Close()
-
- return fmt.Errorf("refstore/files: unsupported write operation %d", item.op.kind)
- }
-
- _, err = lock.WriteString(content)
- if err != nil {
- _ = lock.Close()
-
- return err
- }
-
- err = lock.Close()
- if err != nil {
- return err
- }
-
- dir := path.Dir(item.target.loc.path)
- if dir != "." {
- err = root.MkdirAll(dir, 0o755)
- if err != nil {
- return err
- }
- }
-
- err = executor.removeEmptyDirTree(item.target.loc)
- if err != nil {
- return err
- }
-
- return root.Rename(lockName, item.target.loc.path)
-}
diff --git a/ref/store/files/update_write_packed_refs.go b/ref/store/files/update_write_packed_refs.go
deleted file mode 100644
index c7eea780..00000000
--- a/ref/store/files/update_write_packed_refs.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package files
-
-import (
- "errors"
- "os"
-)
-
-func (executor *refUpdateExecutor) applyPackedRefDeletes(prepared []preparedUpdate) error {
- _, err := executor.store.commonRoot.Stat("packed-refs.lock")
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return nil
- }
-
- return err
- }
-
- packed, err := executor.store.readPackedRefs()
- if err != nil {
- return err
- }
-
- deleted := make(map[string]struct{})
- needed := false
-
- for _, item := range prepared {
- if item.op.kind != updateDelete && item.op.kind != updateDeleteSymbolic {
- continue
- }
-
- deleted[item.target.name] = struct{}{}
- if item.target.ref.isPacked {
- needed = true
- }
- }
-
- if !needed {
- return nil
- }
-
- lock, err := executor.store.commonRoot.OpenFile("packed-refs.new", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
- if err != nil {
- return err
- }
-
- createdTemp := true
-
- defer func() {
- if !createdTemp {
- return
- }
-
- _ = executor.store.commonRoot.Remove("packed-refs.new")
- }()
-
- _, err = lock.WriteString("# pack-refs with: peeled fully-peeled sorted\n")
- if err != nil {
- _ = lock.Close()
-
- return err
- }
-
- for _, entry := range packed.ordered {
- if _, skip := deleted[entry.Name()]; skip {
- continue
- }
-
- _, err = lock.WriteString(entry.ID.String() + " " + entry.Name() + "\n")
- if err != nil {
- _ = lock.Close()
-
- return err
- }
-
- if entry.Peeled != nil {
- _, err = lock.WriteString("^" + entry.Peeled.String() + "\n")
- if err != nil {
- _ = lock.Close()
-
- return err
- }
- }
- }
-
- err = lock.Close()
- if err != nil {
- return err
- }
-
- err = executor.store.commonRoot.Rename("packed-refs.new", "packed-refs")
- if err != nil {
- return err
- }
-
- createdTemp = false
-
- return nil
-}
diff --git a/ref/store/files/worktree_test.go b/ref/store/files/worktree_test.go
deleted file mode 100644
index ae3dc299..00000000
--- a/ref/store/files/worktree_test.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package files_test
-
-import (
- "errors"
- "slices"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func TestFilesWorktreeRefsMatchGit(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
-
- testRepo.Run(t, "commit", "--allow-empty", "-m", "initial")
-
- initialID, err := objectid.ParseHex(algo, testRepo.Run(t, "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(initial HEAD): %v", err)
- }
-
- testRepo.Run(t, "branch", "wt1", initialID.String())
- testRepo.Run(t, "branch", "wt2", initialID.String())
- testRepo.Run(t, "worktree", "add", "wt1", "wt1")
- testRepo.Run(t, "worktree", "add", "wt2", "wt2")
-
- testRepo.Run(t, "-C", "wt1", "commit", "--allow-empty", "-m", "wt1")
- testRepo.Run(t, "-C", "wt2", "commit", "--allow-empty", "-m", "wt2")
-
- wt1ID, err := objectid.ParseHex(algo, testRepo.Run(t, "-C", "wt1", "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(wt1 HEAD): %v", err)
- }
-
- wt2ID, err := objectid.ParseHex(algo, testRepo.Run(t, "-C", "wt2", "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(wt2 HEAD): %v", err)
- }
-
- testRepo.UpdateRef(t, "refs/worktree/foo", initialID)
- testRepo.Run(t, "-C", "wt1", "update-ref", "refs/worktree/foo", wt1ID.String())
- testRepo.Run(t, "-C", "wt2", "update-ref", "refs/worktree/foo", wt2ID.String())
-
- mainStore := openFilesStore(t, testRepo, algo)
- repoRoot := testRepo.OpenRoot(t)
- wt1Store := openFilesStoreAt(t, openGitRootUnder(t, repoRoot, "wt1"), algo)
- wt2Store := openFilesStoreAt(t, openGitRootUnder(t, repoRoot, "wt2"), algo)
-
- got, err := mainStore.ResolveToDetached("refs/worktree/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(main refs/worktree/foo): %v", err)
- }
-
- if got.ID != initialID {
- t.Fatalf("ResolveToDetached(main refs/worktree/foo) = %s, want %s", got.ID, initialID)
- }
-
- got, err = wt1Store.ResolveToDetached("refs/worktree/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(wt1 refs/worktree/foo): %v", err)
- }
-
- if got.ID != wt1ID {
- t.Fatalf("ResolveToDetached(wt1 refs/worktree/foo) = %s, want %s", got.ID, wt1ID)
- }
-
- got, err = wt2Store.ResolveToDetached("refs/worktree/foo")
- if err != nil {
- t.Fatalf("ResolveToDetached(wt2 refs/worktree/foo): %v", err)
- }
-
- if got.ID != wt2ID {
- t.Fatalf("ResolveToDetached(wt2 refs/worktree/foo) = %s, want %s", got.ID, wt2ID)
- }
-
- got, err = wt1Store.ResolveToDetached("main-worktree/HEAD")
- if err != nil {
- t.Fatalf("ResolveToDetached(wt1 main-worktree/HEAD): %v", err)
- }
-
- if got.ID != initialID {
- t.Fatalf("ResolveToDetached(wt1 main-worktree/HEAD) = %s, want %s", got.ID, initialID)
- }
-
- got, err = mainStore.ResolveToDetached("worktrees/wt1/HEAD")
- if err != nil {
- t.Fatalf("ResolveToDetached(main worktrees/wt1/HEAD): %v", err)
- }
-
- if got.ID != wt1ID {
- t.Fatalf("ResolveToDetached(main worktrees/wt1/HEAD) = %s, want %s", got.ID, wt1ID)
- }
-
- got, err = wt2Store.ResolveToDetached("worktrees/wt1/HEAD")
- if err != nil {
- t.Fatalf("ResolveToDetached(wt2 worktrees/wt1/HEAD): %v", err)
- }
-
- if got.ID != wt1ID {
- t.Fatalf("ResolveToDetached(wt2 worktrees/wt1/HEAD) = %s, want %s", got.ID, wt1ID)
- }
-
- assertListMatchesGitForEachRef(t, testRepo.Run(t, "for-each-ref", "--format=%(refname)"), mainStore)
- assertListMatchesGitForEachRef(t, testRepo.Run(t, "-C", "wt1", "for-each-ref", "--format=%(refname)"), wt1Store)
- })
-}
-
-func TestFilesTransactionPerWorktreeRefsMatchGit(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, RefFormat: "files"})
- testRepo.Run(t, "commit", "--allow-empty", "-m", "initial")
- testRepo.Run(t, "branch", "wt1", "HEAD")
- testRepo.Run(t, "worktree", "add", "wt1", "wt1")
-
- mainID, err := objectid.ParseHex(algo, testRepo.Run(t, "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(main HEAD): %v", err)
- }
-
- testRepo.Run(t, "-C", "wt1", "commit", "--allow-empty", "-m", "wt1")
-
- wt1ID, err := objectid.ParseHex(algo, testRepo.Run(t, "-C", "wt1", "rev-parse", "HEAD"))
- if err != nil {
- t.Fatalf("ParseHex(wt1 HEAD): %v", err)
- }
-
- mainStore := openFilesStore(t, testRepo, algo)
- repoRoot := testRepo.OpenRoot(t)
- wt1Store := openFilesStoreAt(t, openGitRootUnder(t, repoRoot, "wt1"), algo)
-
- mainTx, err := mainStore.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(main): %v", err)
- }
-
- err = mainTx.Create("refs/bisect/main-only", mainID)
- if err != nil {
- t.Fatalf("Create(main-only) queue: %v", err)
- }
-
- err = mainTx.Commit()
- if err != nil {
- t.Fatalf("Commit(main-only): %v", err)
- }
-
- wtTx, err := wt1Store.BeginTransaction()
- if err != nil {
- t.Fatalf("BeginTransaction(wt1): %v", err)
- }
-
- err = wtTx.Create("refs/bisect/wt-only", wt1ID)
- if err != nil {
- t.Fatalf("Create(wt-only) queue: %v", err)
- }
-
- err = wtTx.Commit()
- if err != nil {
- t.Fatalf("Commit(wt-only): %v", err)
- }
-
- got, err := mainStore.ResolveToDetached("refs/bisect/main-only")
- if err != nil {
- t.Fatalf("ResolveToDetached(main-only): %v", err)
- }
-
- if got.ID != mainID {
- t.Fatalf("ResolveToDetached(main-only) = %s, want %s", got.ID, mainID)
- }
-
- got, err = wt1Store.ResolveToDetached("refs/bisect/wt-only")
- if err != nil {
- t.Fatalf("ResolveToDetached(wt-only): %v", err)
- }
-
- if got.ID != wt1ID {
- t.Fatalf("ResolveToDetached(wt-only) = %s, want %s", got.ID, wt1ID)
- }
-
- _, err = mainStore.Resolve("refs/bisect/wt-only")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(main sees wt-only) error = %v, want ErrReferenceNotFound", err)
- }
-
- _, err = wt1Store.Resolve("refs/bisect/main-only")
- if !errors.Is(err, refstore.ErrReferenceNotFound) {
- t.Fatalf("Resolve(wt sees main-only) error = %v, want ErrReferenceNotFound", err)
- }
-
- mainRefs := forEachRefLines(testRepo.Run(t, "for-each-ref", "--format=%(refname)", "refs/bisect"))
-
- wtRefs := forEachRefLines(testRepo.Run(t, "-C", "wt1", "for-each-ref", "--format=%(refname)", "refs/bisect"))
- if !slices.Equal(mainRefs, []string{"refs/bisect/main-only"}) {
- t.Fatalf("main for-each-ref refs/bisect = %v", mainRefs)
- }
-
- if !slices.Equal(wtRefs, []string{"refs/bisect/wt-only"}) {
- t.Fatalf("wt1 for-each-ref refs/bisect = %v", wtRefs)
- }
- })
-}
diff --git a/ref/store/reading.go b/ref/store/reading.go
deleted file mode 100644
index a9e2ad49..00000000
--- a/ref/store/reading.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package refstore
-
-import "codeberg.org/lindenii/furgit/ref"
-
-// Reader reads Git references.
-//
-// Labels: MT-Safe.
-type Reader interface {
- // Resolve resolves a reference name to either a symbolic or detached ref.
- //
- // Implementations should return value forms ([ref.Detached] or [ref.Symbolic]),
- // not pointer forms. If the reference does not exist, implementations should
- // return [ErrReferenceNotFound].
- //
- // Labels: Life-Parent.
- Resolve(name string) (ref.Ref, error)
- // ResolveToDetached resolves a reference name to a detached object ID.
- //
- // Implementations may use backend-local lookup semantics for symbolic hops.
- // Callers that need cross-backend symbolic resolution (for example in a
- // chain of stores) should prefer repeatedly calling Resolve.
- //
- // ResolveToDetached resolves symbolic references only. It does not imply peeling
- // annotated tag objects.
- //
- // Labels: Life-Parent.
- ResolveToDetached(name string) (ref.Detached, error)
- // List returns references matching pattern.
- //
- // The exact pattern language is backend-defined.
- //
- // Labels: Life-Parent.
- List(pattern string) ([]ref.Ref, error)
-}
diff --git a/ref/store/transaction.go b/ref/store/transaction.go
deleted file mode 100644
index 30f6ab50..00000000
--- a/ref/store/transaction.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package refstore
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Transaction stages reference updates for one atomic commit.
-//
-// A transaction borrows its underlying store and is invalid after that store
-// is closed.
-//
-// Ordinary methods operate in dereference mode if name resolves to
-// a symbolic ref, the operation applies to the final referent rather
-// than to the symbolic ref itself.
-//
-// Symbolic methods operate on the named reference directly, without
-// dereferencing symbolic refs.
-//
-// Labels: MT-Unsafe.
-type Transaction interface {
- // Create creates one detached reference, requiring that the logical
- // reference does not already exist.
- Create(name string, newID objectid.ObjectID) error
- // Update updates one detached reference, requiring that the current logical
- // reference value matches oldID.
- Update(name string, newID, oldID objectid.ObjectID) error
- // Delete deletes one detached reference, requiring that the current logical
- // reference value matches oldID.
- Delete(name string, oldID objectid.ObjectID) error
- // Verify verifies that the current logical reference value matches oldID.
- Verify(name string, oldID objectid.ObjectID) error
-
- // CreateSymbolic creates one symbolic reference, requiring that the named
- // reference does not already exist.
- CreateSymbolic(name, newTarget string) error
- // UpdateSymbolic updates one symbolic reference directly, requiring that its
- // current target matches oldTarget.
- UpdateSymbolic(name, newTarget, oldTarget string) error
- // DeleteSymbolic deletes one symbolic reference directly, requiring that its
- // current target matches oldTarget.
- DeleteSymbolic(name, oldTarget string) error
- // VerifySymbolic verifies that the named symbolic reference currently points
- // at oldTarget.
- VerifySymbolic(name, oldTarget string) error
-
- // Commit validates and applies all queued operations atomically.
- //
- // Commit invalidates the receiver.
- Commit() error
- // Abort abandons the transaction and releases any resources it holds.
- //
- // Abort invalidates the receiver.
- Abort() error
-}
diff --git a/ref/store/transactional_store.go b/ref/store/transactional_store.go
deleted file mode 100644
index 10f4543a..00000000
--- a/ref/store/transactional_store.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package refstore
-
-// Transactioner begins atomic reference transactions.
-//
-// Not every readable reference store is writable. Implementations should only
-// satisfy Transactioner when they can stage and commit reference updates
-// atomically within that backend.
-type Transactioner interface {
- // BeginTransaction creates one new mutable transaction.
- //
- // Labels: Life-Parent.
- BeginTransaction() (Transaction, error)
-}
diff --git a/ref/store/update_errors.go b/ref/store/update_errors.go
deleted file mode 100644
index f05f37d2..00000000
--- a/ref/store/update_errors.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package refstore
-
-import "fmt"
-
-// InvalidNameError indicates that one requested reference name is invalid.
-type InvalidNameError struct {
- Err error
-}
-
-func (err *InvalidNameError) Error() string {
- if err == nil || err.Err == nil {
- return "invalid reference name"
- }
-
- return fmt.Sprintf("invalid reference name: %v", err.Err)
-}
-
-func (err *InvalidNameError) Unwrap() error {
- if err == nil {
- return nil
- }
-
- return err.Err
-}
-
-// InvalidValueError indicates that one requested reference value is invalid.
-type InvalidValueError struct {
- Err error
-}
-
-func (err *InvalidValueError) Error() string {
- if err == nil || err.Err == nil {
- return "invalid reference value"
- }
-
- return fmt.Sprintf("invalid reference value: %v", err.Err)
-}
-
-func (err *InvalidValueError) Unwrap() error {
- if err == nil {
- return nil
- }
-
- return err.Err
-}
-
-// DuplicateUpdateError indicates that one batch or transaction includes a
-// duplicate update target.
-type DuplicateUpdateError struct{}
-
-func (err *DuplicateUpdateError) Error() string {
- return "duplicate reference update"
-}
-
-// CreateExistsError indicates that one create operation targeted an existing
-// reference.
-type CreateExistsError struct{}
-
-func (err *CreateExistsError) Error() string {
- return "reference already exists"
-}
-
-// IncorrectOldValueError indicates that one operation's expected old value did
-// not match the current reference value.
-type IncorrectOldValueError struct {
- Actual string
- Expected string
-}
-
-func (err *IncorrectOldValueError) Error() string {
- if err == nil {
- return "incorrect old value provided"
- }
-
- if err.Actual == "" && err.Expected == "" {
- return "incorrect old value provided"
- }
-
- return fmt.Sprintf("incorrect old value provided: got %q, expected %q", err.Actual, err.Expected)
-}
-
-// ExpectedDetachedError indicates that one operation required a detached
-// reference but found a different kind.
-type ExpectedDetachedError struct{}
-
-func (err *ExpectedDetachedError) Error() string {
- return "expected detached reference"
-}
-
-// ExpectedSymbolicError indicates that one operation required a symbolic
-// reference but found a different kind.
-type ExpectedSymbolicError struct{}
-
-func (err *ExpectedSymbolicError) Error() string {
- return "expected symbolic reference"
-}
-
-// NameConflictError indicates that one reference name conflicts with another
-// visible or queued reference name.
-type NameConflictError struct {
- Other string
-}
-
-func (err *NameConflictError) Error() string {
- if err == nil || err.Other == "" {
- return "reference name conflict"
- }
-
- return fmt.Sprintf("reference name conflict with %q", err.Other)
-}
diff --git a/ref/symbolic.go b/ref/symbolic.go
deleted file mode 100644
index af9f9e84..00000000
--- a/ref/symbolic.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package ref
-
-// Symbolic points to another reference name.
-type Symbolic struct {
- RefName string
- Target string
-}
-
-// Name returns the fully-qualified reference name.
-func (ref Symbolic) Name() string {
- return ref.RefName
-}
-
-func (Symbolic) isRef() {}