github.com/go-git/go-git/v6@v6.0.0-20251216093047-22c365fcee9c/plumbing/reference.go (about) 1 package plumbing 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strings" 8 ) 9 10 const ( 11 refPrefix = "refs/" 12 refHeadPrefix = refPrefix + "heads/" 13 refTagPrefix = refPrefix + "tags/" 14 refRemotePrefix = refPrefix + "remotes/" 15 refNotePrefix = refPrefix + "notes/" 16 symrefPrefix = "ref: " 17 ) 18 19 // RefRevParseRules are a set of rules to parse references into short names, or expand into a full reference. 20 // These are the same rules as used by git in shorten_unambiguous_ref and expand_ref. 21 // See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 22 var RefRevParseRules = []string{ 23 "%s", 24 "refs/%s", 25 "refs/tags/%s", 26 "refs/heads/%s", 27 "refs/remotes/%s", 28 "refs/remotes/%s/HEAD", 29 } 30 31 var ( 32 // ErrReferenceNotFound is returned when a reference is not found. 33 ErrReferenceNotFound = errors.New("reference not found") 34 35 // ErrInvalidReferenceName is returned when a reference name is invalid. 36 ErrInvalidReferenceName = errors.New("invalid reference name") 37 ) 38 39 // ReferenceType reference type's 40 type ReferenceType int8 41 42 const ( 43 // InvalidReference represents an invalid reference type. 44 InvalidReference ReferenceType = 0 45 // HashReference represents a hash reference. 46 HashReference ReferenceType = 1 47 // SymbolicReference represents a symbolic reference. 48 SymbolicReference ReferenceType = 2 49 ) 50 51 func (r ReferenceType) String() string { 52 switch r { 53 case InvalidReference: 54 return "invalid-reference" 55 case HashReference: 56 return "hash-reference" 57 case SymbolicReference: 58 return "symbolic-reference" 59 } 60 61 return "" 62 } 63 64 // ReferenceName reference name's 65 type ReferenceName string 66 67 // NewBranchReferenceName returns a reference name describing a branch based on 68 // his short name. 69 func NewBranchReferenceName(name string) ReferenceName { 70 return ReferenceName(refHeadPrefix + name) 71 } 72 73 // NewNoteReferenceName returns a reference name describing a note based on his 74 // short name. 75 func NewNoteReferenceName(name string) ReferenceName { 76 return ReferenceName(refNotePrefix + name) 77 } 78 79 // NewRemoteReferenceName returns a reference name describing a remote branch 80 // based on his short name and the remote name. 81 func NewRemoteReferenceName(remote, name string) ReferenceName { 82 return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, name)) 83 } 84 85 // NewRemoteHEADReferenceName returns a reference name describing a the HEAD 86 // branch of a remote. 87 func NewRemoteHEADReferenceName(remote string) ReferenceName { 88 return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, HEAD)) 89 } 90 91 // NewTagReferenceName returns a reference name describing a tag based on short 92 // his name. 93 func NewTagReferenceName(name string) ReferenceName { 94 return ReferenceName(refTagPrefix + name) 95 } 96 97 // IsBranch check if a reference is a branch 98 func (r ReferenceName) IsBranch() bool { 99 return strings.HasPrefix(string(r), refHeadPrefix) 100 } 101 102 // IsNote check if a reference is a note 103 func (r ReferenceName) IsNote() bool { 104 return strings.HasPrefix(string(r), refNotePrefix) 105 } 106 107 // IsRemote check if a reference is a remote 108 func (r ReferenceName) IsRemote() bool { 109 return strings.HasPrefix(string(r), refRemotePrefix) 110 } 111 112 // IsTag check if a reference is a tag 113 func (r ReferenceName) IsTag() bool { 114 return strings.HasPrefix(string(r), refTagPrefix) 115 } 116 117 func (r ReferenceName) String() string { 118 return string(r) 119 } 120 121 // Short returns the short name of a ReferenceName 122 func (r ReferenceName) Short() string { 123 s := string(r) 124 res := s 125 for _, format := range RefRevParseRules[1:] { 126 _, err := fmt.Sscanf(s, format, &res) 127 if err == nil { 128 continue 129 } 130 } 131 132 return res 133 } 134 135 var ctrlSeqs = regexp.MustCompile(`[\000-\037\177]`) 136 137 // Validate validates a reference name. 138 // This follows the git-check-ref-format rules. 139 // See https://git-scm.com/docs/git-check-ref-format 140 // 141 // It is important to note that this function does not check if the reference 142 // exists in the repository. 143 // It only checks if the reference name is valid. 144 // This functions does not support the --refspec-pattern, --normalize, and 145 // --allow-onelevel options. 146 // 147 // Git imposes the following rules on how references are named: 148 // 149 // 1. They can include slash / for hierarchical (directory) grouping, but no 150 // slash-separated component can begin with a dot . or end with the 151 // sequence .lock. 152 // 2. They must contain at least one /. This enforces the presence of a 153 // category like heads/, tags/ etc. but the actual names are not 154 // restricted. If the --allow-onelevel option is used, this rule is 155 // waived. 156 // 3. They cannot have two consecutive dots .. anywhere. 157 // 4. They cannot have ASCII control characters (i.e. bytes whose values are 158 // lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : 159 // anywhere. 160 // 5. They cannot have question-mark ?, asterisk *, or open bracket [ 161 // anywhere. See the --refspec-pattern option below for an exception to this 162 // rule. 163 // 6. They cannot begin or end with a slash / or contain multiple consecutive 164 // slashes (see the --normalize option below for an exception to this rule). 165 // 7. They cannot end with a dot .. 166 // 8. They cannot contain a sequence @{. 167 // 9. They cannot be the single character @. 168 // 10. They cannot contain a \. 169 func (r ReferenceName) Validate() error { 170 s := string(r) 171 if len(s) == 0 { 172 return ErrInvalidReferenceName 173 } 174 175 // HEAD is a special case 176 if r == HEAD { 177 return nil 178 } 179 180 // rule 7 181 if strings.HasSuffix(s, ".") { 182 return ErrInvalidReferenceName 183 } 184 185 // rule 2 186 parts := strings.Split(s, "/") 187 if len(parts) < 2 { 188 return ErrInvalidReferenceName 189 } 190 191 isBranch := r.IsBranch() 192 isTag := r.IsTag() 193 for i, part := range parts { 194 // rule 6 195 if len(part) == 0 { 196 return ErrInvalidReferenceName 197 } 198 199 if strings.HasPrefix(part, ".") || // rule 1 200 strings.Contains(part, "..") || // rule 3 201 ctrlSeqs.MatchString(part) || // rule 4 202 strings.ContainsAny(part, "~^:?*[ \t\n") || // rule 4 & 5 203 strings.Contains(part, "@{") || // rule 8 204 part == "@" || // rule 9 205 strings.Contains(part, "\\") || // rule 10 206 strings.HasSuffix(part, ".lock") { // rule 1 207 return ErrInvalidReferenceName 208 } 209 210 if (isBranch || isTag) && strings.HasPrefix(part, "-") && (i == 2) { // branches & tags can't start with - 211 return ErrInvalidReferenceName 212 } 213 } 214 215 return nil 216 } 217 218 const ( 219 // HEAD is the special reference pointing to the current branch. 220 HEAD ReferenceName = "HEAD" 221 // Master is the master branch reference. 222 Master ReferenceName = "refs/heads/master" 223 // Main is the main branch reference. 224 Main ReferenceName = "refs/heads/main" 225 ) 226 227 // Reference is a representation of git reference 228 type Reference struct { 229 t ReferenceType 230 n ReferenceName 231 h Hash 232 target ReferenceName 233 } 234 235 // NewReferenceFromStrings creates a reference from name and target as string, 236 // the resulting reference can be a SymbolicReference or a HashReference base 237 // on the target provided 238 func NewReferenceFromStrings(name, target string) *Reference { 239 n := ReferenceName(name) 240 241 if strings.HasPrefix(target, symrefPrefix) { 242 target := ReferenceName(target[len(symrefPrefix):]) 243 return NewSymbolicReference(n, target) 244 } 245 246 return NewHashReference(n, NewHash(target)) 247 } 248 249 // NewSymbolicReference creates a new SymbolicReference reference 250 func NewSymbolicReference(n, target ReferenceName) *Reference { 251 return &Reference{ 252 t: SymbolicReference, 253 n: n, 254 target: target, 255 } 256 } 257 258 // NewHashReference creates a new HashReference reference 259 func NewHashReference(n ReferenceName, h Hash) *Reference { 260 return &Reference{ 261 t: HashReference, 262 n: n, 263 h: h, 264 } 265 } 266 267 // Type returns the type of a reference 268 func (r *Reference) Type() ReferenceType { 269 return r.t 270 } 271 272 // Name returns the name of a reference 273 func (r *Reference) Name() ReferenceName { 274 return r.n 275 } 276 277 // Hash returns the hash of a hash reference 278 func (r *Reference) Hash() Hash { 279 return r.h 280 } 281 282 // Target returns the target of a symbolic reference 283 func (r *Reference) Target() ReferenceName { 284 return r.target 285 } 286 287 // Strings dump a reference as a [2]string 288 func (r *Reference) Strings() [2]string { 289 var o [2]string 290 o[0] = r.Name().String() 291 292 switch r.Type() { 293 case HashReference: 294 o[1] = r.Hash().String() 295 case SymbolicReference: 296 o[1] = symrefPrefix + r.Target().String() 297 } 298 299 return o 300 } 301 302 func (r *Reference) String() string { 303 ref := "" 304 switch r.Type() { 305 case HashReference: 306 ref = r.Hash().String() 307 case SymbolicReference: 308 ref = symrefPrefix + r.Target().String() 309 default: 310 return "" 311 } 312 313 name := r.Name().String() 314 var v strings.Builder 315 v.Grow(len(ref) + len(name) + 1) 316 v.WriteString(ref) 317 v.WriteString(" ") 318 v.WriteString(name) 319 return v.String() 320 }