@@ -3,6 +3,7 @@ package plumbing
33import (
44 "errors"
55 "fmt"
6+ "regexp"
67 "strings"
78)
89
@@ -29,6 +30,9 @@ var RefRevParseRules = []string{
2930
3031var (
3132 ErrReferenceNotFound = errors .New ("reference not found" )
33+
34+ // ErrInvalidReferenceName is returned when a reference name is invalid.
35+ ErrInvalidReferenceName = errors .New ("invalid reference name" )
3236)
3337
3438// ReferenceType reference type's
@@ -124,6 +128,91 @@ func (r ReferenceName) Short() string {
124128 return res
125129}
126130
131+ var (
132+ ctrlSeqs = regexp .MustCompile (`[\000-\037\177]` )
133+ )
134+
135+ // Validate validates a reference name.
136+ // This follows the git-check-ref-format rules.
137+ // See https://git-scm.com/docs/git-check-ref-format
138+ //
139+ // It is important to note that this function does not check if the reference
140+ // exists in the repository.
141+ // It only checks if the reference name is valid.
142+ // This functions does not support the --refspec-pattern, --normalize, and
143+ // --allow-onelevel options.
144+ //
145+ // Git imposes the following rules on how references are named:
146+ //
147+ // 1. They can include slash / for hierarchical (directory) grouping, but no
148+ // slash-separated component can begin with a dot . or end with the
149+ // sequence .lock.
150+ // 2. They must contain at least one /. This enforces the presence of a
151+ // category like heads/, tags/ etc. but the actual names are not
152+ // restricted. If the --allow-onelevel option is used, this rule is
153+ // waived.
154+ // 3. They cannot have two consecutive dots .. anywhere.
155+ // 4. They cannot have ASCII control characters (i.e. bytes whose values are
156+ // lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon :
157+ // anywhere.
158+ // 5. They cannot have question-mark ?, asterisk *, or open bracket [
159+ // anywhere. See the --refspec-pattern option below for an exception to this
160+ // rule.
161+ // 6. They cannot begin or end with a slash / or contain multiple consecutive
162+ // slashes (see the --normalize option below for an exception to this rule).
163+ // 7. They cannot end with a dot ..
164+ // 8. They cannot contain a sequence @{.
165+ // 9. They cannot be the single character @.
166+ // 10. They cannot contain a \.
167+ func (r ReferenceName ) Validate () error {
168+ s := string (r )
169+ if len (s ) == 0 {
170+ return ErrInvalidReferenceName
171+ }
172+
173+ // HEAD is a special case
174+ if r == HEAD {
175+ return nil
176+ }
177+
178+ // rule 7
179+ if strings .HasSuffix (s , "." ) {
180+ return ErrInvalidReferenceName
181+ }
182+
183+ // rule 2
184+ parts := strings .Split (s , "/" )
185+ if len (parts ) < 2 {
186+ return ErrInvalidReferenceName
187+ }
188+
189+ isBranch := r .IsBranch ()
190+ isTag := r .IsTag ()
191+ for _ , part := range parts {
192+ // rule 6
193+ if len (part ) == 0 {
194+ return ErrInvalidReferenceName
195+ }
196+
197+ if strings .HasPrefix (part , "." ) || // rule 1
198+ strings .Contains (part , ".." ) || // rule 3
199+ ctrlSeqs .MatchString (part ) || // rule 4
200+ strings .ContainsAny (part , "~^:?*[ \t \n " ) || // rule 4 & 5
201+ strings .Contains (part , "@{" ) || // rule 8
202+ part == "@" || // rule 9
203+ strings .Contains (part , "\\ " ) || // rule 10
204+ strings .HasSuffix (part , ".lock" ) { // rule 1
205+ return ErrInvalidReferenceName
206+ }
207+
208+ if (isBranch || isTag ) && strings .HasPrefix (part , "-" ) { // branches & tags can't start with -
209+ return ErrInvalidReferenceName
210+ }
211+ }
212+
213+ return nil
214+ }
215+
127216const (
128217 HEAD ReferenceName = "HEAD"
129218 Master ReferenceName = "refs/heads/master"
0 commit comments