aboutsummaryrefslogtreecommitdiff
path: root/ref/refname
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-26 09:19:01 +0000
committerGravatar Runxi Yu2026-03-26 09:19:01 +0000
commit929b8cc620abca70b3444b09be5249f6c6cb7812 (patch)
tree29b1edba8f597d09a2a51a6b2c901389cc40e242 /ref/refname
parentformat: Move commitgraph and packfile here (diff)
signatureNo signature
ref/name: Rename from ref/refname
Diffstat (limited to 'ref/refname')
-rw-r--r--ref/refname/branch.go25
-rw-r--r--ref/refname/component.go88
-rw-r--r--ref/refname/current.go5
-rw-r--r--ref/refname/disposition.go20
-rw-r--r--ref/refname/doc.go2
-rw-r--r--ref/refname/errors.go14
-rw-r--r--ref/refname/flags.go6
-rw-r--r--ref/refname/length.go11
-rw-r--r--ref/refname/lock.go3
-rw-r--r--ref/refname/normalize.go53
-rw-r--r--ref/refname/options.go30
-rw-r--r--ref/refname/pseudo.go11
-rw-r--r--ref/refname/refname_test.go622
-rw-r--r--ref/refname/root.go21
-rw-r--r--ref/refname/root_syntax.go13
-rw-r--r--ref/refname/safe.go31
-rw-r--r--ref/refname/sanitize.go19
-rw-r--r--ref/refname/slashes.go26
-rw-r--r--ref/refname/tag.go20
-rw-r--r--ref/refname/update.go56
-rw-r--r--ref/refname/utils.go22
-rw-r--r--ref/refname/validate.go65
-rw-r--r--ref/refname/worktree.go75
23 files changed, 0 insertions, 1238 deletions
diff --git a/ref/refname/branch.go b/ref/refname/branch.go
deleted file mode 100644
index 274a95e3..00000000
--- a/ref/refname/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/refname/component.go b/ref/refname/component.go
deleted file mode 100644
index f5adba46..00000000
--- a/ref/refname/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/refname/current.go b/ref/refname/current.go
deleted file mode 100644
index 3a5394cc..00000000
--- a/ref/refname/current.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package refname
-
-func isCurrentWorktreeRef(name string) bool {
- return IsRootSyntax(name) || IsPerWorktree(name)
-}
diff --git a/ref/refname/disposition.go b/ref/refname/disposition.go
deleted file mode 100644
index 5153e633..00000000
--- a/ref/refname/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/refname/doc.go b/ref/refname/doc.go
deleted file mode 100644
index 363733f3..00000000
--- a/ref/refname/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package refname provides various routines to check and normalize reference names.
-package refname
diff --git a/ref/refname/errors.go b/ref/refname/errors.go
deleted file mode 100644
index e39bc73b..00000000
--- a/ref/refname/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/refname/flags.go b/ref/refname/flags.go
deleted file mode 100644
index 72f0a58f..00000000
--- a/ref/refname/flags.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package refname
-
-const (
- refnameAllowOneLevel = 1 << iota
- refnameRefspecPattern
-)
diff --git a/ref/refname/length.go b/ref/refname/length.go
deleted file mode 100644
index 94c0322b..00000000
--- a/ref/refname/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/refname/lock.go b/ref/refname/lock.go
deleted file mode 100644
index 33db902f..00000000
--- a/ref/refname/lock.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package refname
-
-const lockSuffix = ".lock"
diff --git a/ref/refname/normalize.go b/ref/refname/normalize.go
deleted file mode 100644
index 9cbe7126..00000000
--- a/ref/refname/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/refname/options.go b/ref/refname/options.go
deleted file mode 100644
index 5ae81541..00000000
--- a/ref/refname/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/refname/pseudo.go b/ref/refname/pseudo.go
deleted file mode 100644
index f0ad1ae8..00000000
--- a/ref/refname/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/refname/refname_test.go b/ref/refname/refname_test.go
deleted file mode 100644
index 5b26196f..00000000
--- a/ref/refname/refname_test.go
+++ /dev/null
@@ -1,622 +0,0 @@
-package refname_test
-
-import (
- "context"
- "os/exec"
- "strings"
- "testing"
-
- "codeberg.org/lindenii/furgit/ref/refname"
-)
-
-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/refname/root.go b/ref/refname/root.go
deleted file mode 100644
index 43361846..00000000
--- a/ref/refname/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/refname/root_syntax.go b/ref/refname/root_syntax.go
deleted file mode 100644
index 97a15cb9..00000000
--- a/ref/refname/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/refname/safe.go b/ref/refname/safe.go
deleted file mode 100644
index b36d3b2f..00000000
--- a/ref/refname/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/refname/sanitize.go b/ref/refname/sanitize.go
deleted file mode 100644
index f543de7c..00000000
--- a/ref/refname/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/refname/slashes.go b/ref/refname/slashes.go
deleted file mode 100644
index 44d3e4ea..00000000
--- a/ref/refname/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/refname/tag.go b/ref/refname/tag.go
deleted file mode 100644
index 226c0fdd..00000000
--- a/ref/refname/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/refname/update.go b/ref/refname/update.go
deleted file mode 100644
index 92830f1a..00000000
--- a/ref/refname/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/refname/utils.go b/ref/refname/utils.go
deleted file mode 100644
index 58944748..00000000
--- a/ref/refname/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/refname/validate.go b/ref/refname/validate.go
deleted file mode 100644
index 1b8ad396..00000000
--- a/ref/refname/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/refname/worktree.go b/ref/refname/worktree.go
deleted file mode 100644
index 48ca215d..00000000
--- a/ref/refname/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,
- }
-}