shithub: furgit

Download patch

ref: 9d08dc994d51298e2d8e75d8ed4ee477312ec53a
parent: 9d8e9f07083e3e3c85f27bc0583258bce0266509
author: Runxi Yu <me@runxiyu.org>
date: Sat Mar 7 10:08:14 EST 2026

ref/refname: Add refname validation

--- /dev/null
+++ b/ref/refname/branch.go
@@ -1,0 +1,25 @@
+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
+}
--- /dev/null
+++ b/ref/refname/component.go
@@ -1,0 +1,88 @@
+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
+}
--- /dev/null
+++ b/ref/refname/current.go
@@ -1,0 +1,5 @@
+package refname
+
+func isCurrentWorktreeRef(name string) bool {
+	return IsRootSyntax(name) || IsPerWorktree(name)
+}
--- /dev/null
+++ b/ref/refname/disposition.go
@@ -1,0 +1,20 @@
+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
+	}
+}
--- /dev/null
+++ b/ref/refname/errors.go
@@ -1,0 +1,14 @@
+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)
+}
--- /dev/null
+++ b/ref/refname/flags.go
@@ -1,0 +1,6 @@
+package refname
+
+const (
+	refnameAllowOneLevel = 1 << iota
+	refnameRefspecPattern
+)
--- /dev/null
+++ b/ref/refname/length.go
@@ -1,0 +1,11 @@
+package refname
+
+import "strings"
+
+func sanitizedLen(builder *strings.Builder) int {
+	if builder == nil {
+		return 0
+	}
+
+	return builder.Len()
+}
--- /dev/null
+++ b/ref/refname/lock.go
@@ -1,0 +1,3 @@
+package refname
+
+const lockSuffix = ".lock"
--- /dev/null
+++ b/ref/refname/normalize.go
@@ -1,0 +1,53 @@
+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
+}
--- /dev/null
+++ b/ref/refname/options.go
@@ -1,0 +1,30 @@
+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
+}
--- /dev/null
+++ b/ref/refname/pseudo.go
@@ -1,0 +1,11 @@
+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
+	}
+}
--- /dev/null
+++ b/ref/refname/refname_test.go
@@ -1,0 +1,622 @@
+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
+}
--- /dev/null
+++ b/ref/refname/root.go
@@ -1,0 +1,21 @@
+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
+	}
+}
--- /dev/null
+++ b/ref/refname/root_syntax.go
@@ -1,0 +1,13 @@
+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
+}
--- /dev/null
+++ b/ref/refname/safe.go
@@ -1,0 +1,31 @@
+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
+}
--- /dev/null
+++ b/ref/refname/sanitize.go
@@ -1,0 +1,19 @@
+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()
+}
--- /dev/null
+++ b/ref/refname/slashes.go
@@ -1,0 +1,26 @@
+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()
+}
--- /dev/null
+++ b/ref/refname/tag.go
@@ -1,0 +1,20 @@
+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
+}
--- /dev/null
+++ b/ref/refname/update.go
@@ -1,0 +1,56 @@
+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"}
+}
--- /dev/null
+++ b/ref/refname/utils.go
@@ -1,0 +1,22 @@
+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])
+}
--- /dev/null
+++ b/ref/refname/validate.go
@@ -1,0 +1,65 @@
+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
+}
--- /dev/null
+++ b/ref/refname/worktree.go
@@ -1,0 +1,75 @@
+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,
+	}
+}
--