164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
// Copyright (C) 2024 Umorpha Systems
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package gitcache
|
|
|
|
import (
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
// ValidateRef validates that a ref name is of the correct syntax;
|
|
// much like git-check-ref-format(1).
|
|
//
|
|
// There are a few differences from git-check-ref-format(1):
|
|
//
|
|
// - We accept the special ref name "HEAD" (even though it does not
|
|
// contain a "/")
|
|
//
|
|
// - We require that Ref names must not just contain a "/", but must
|
|
// start with "refs/".
|
|
//
|
|
// - If the `glob` argument is true, then a few restrictions are
|
|
// relaxed:
|
|
//
|
|
// The bans on the characters '*' and '?' are removed.
|
|
//
|
|
// The ban on the character '[' is relaxed; it is allowed when it
|
|
// opens a valid character-class specification in the [path.Match]
|
|
// syntax, with the exception that '!' is the negation character
|
|
// rather than '^'.
|
|
//
|
|
// If the ref name is just "*", then it does not need to have the
|
|
// "refs/" prefix.
|
|
func ValidateRef(ref string, glob bool) bool {
|
|
if ref == "HEAD" {
|
|
return true
|
|
}
|
|
if glob {
|
|
if ref == "*" {
|
|
return true
|
|
}
|
|
if _, err := path.Match(strings.ReplaceAll(ref, "[!", "[^"), ""); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// git-check-ref-format(1) documents 10 rules:
|
|
|
|
// 2, 9. must contain a slash, must not be "@" (we are stricter).
|
|
if !strings.HasPrefix(ref, "refs/") {
|
|
return false
|
|
}
|
|
// 1, 6. parts
|
|
for _, part := range strings.Split(ref, "/") {
|
|
if part == "" || strings.HasPrefix(part, ".") || strings.HasSuffix(part, ".lock") {
|
|
return false
|
|
}
|
|
}
|
|
// 3. Can't contain ".."
|
|
if strings.Contains(ref, "..") {
|
|
return false
|
|
}
|
|
// 4(a), ASCII Control characters
|
|
for i := 0; i < len(ref); i++ {
|
|
c := ref[i]
|
|
if c < 040 || c == 0177 {
|
|
return false
|
|
}
|
|
}
|
|
// 4(b), 5, 10. space, ~, ^, :, ?, *, [, \
|
|
if strings.ContainsAny(ref, " ~^:\\") {
|
|
return false
|
|
}
|
|
if !glob && strings.ContainsAny(ref, "?*[") {
|
|
return false
|
|
}
|
|
// 7. end with dot
|
|
if strings.HasSuffix(ref, ".") {
|
|
return false
|
|
}
|
|
// 8. "@{"
|
|
if strings.Contains(ref, "@{") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isLiteral(glob string) bool {
|
|
return !strings.ContainsAny(glob, "?*[")
|
|
}
|
|
|
|
// MatchRef returns whether the `refglob` glob (which must be in
|
|
// ValidateRef(refglob, true) syntax) matches the `refname` string
|
|
// (which must be in ValidateRef(refname, false) syntax. If either
|
|
// argument has invalid syntax, then false is returned.
|
|
func MatchRef(refglob, refname string) bool {
|
|
if !ValidateRef(refglob, true) || !ValidateRef(refname, false) {
|
|
return false
|
|
}
|
|
if isLiteral(refglob) { // fast-path
|
|
return refname == refglob
|
|
}
|
|
matched, _ := path.Match(strings.ReplaceAll(refglob, "[!", "[^"), refname)
|
|
return matched
|
|
}
|
|
|
|
// ParseRev splits a rev, as specified by gitrevisions(7), into its
|
|
// "ref" part and its "suffix" part.
|
|
//
|
|
// This accepts a subset of the syntax doumented in gitrevisions(7):
|
|
//
|
|
// - We don't accept raw hashs, things must be in terms of a ref.
|
|
// - We don't accept `git describe` output.
|
|
// - We don't accept abbreviated ref names, they must be the full ref
|
|
// name.
|
|
// - Of the special "ref/"-les ref names, we only accept "HEAD", not
|
|
// "FETCH_HEAD" or any of the others.
|
|
// - We don't accept the ":/<text>" syntax for searching commit
|
|
// messages.
|
|
// - We don't accept the ":[<n>:]<path>" syntax for referring to the
|
|
// index.
|
|
//
|
|
// As an extension to gitrevisions(7), if the `glob` argument is true,
|
|
// it then in place of a ref name, it accepts a glob that would match
|
|
// a ref name; see [ValidateRef] for details.
|
|
//
|
|
// BUG(lukeshu): ParseRev does no validation of the suffix.
|
|
func ParseRev(rev string, glob bool) (ref, suffix string, ok bool) {
|
|
if rev == "@" {
|
|
return "HEAD", "", true
|
|
}
|
|
|
|
// - "@{" : reflog
|
|
// - "^" : peel
|
|
// - "~" : peel
|
|
// - ":" : object
|
|
var idx int
|
|
for start := 0; ; {
|
|
idx = strings.IndexAny(rev[start:], "@^~:")
|
|
if idx >= 0 {
|
|
idx += start
|
|
if rev[idx] == '@' && !strings.HasPrefix(rev[idx:], "@{") {
|
|
start = idx + 1
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
if idx < 0 {
|
|
idx = len(rev)
|
|
}
|
|
|
|
ref = rev[:idx]
|
|
suffix = rev[idx:]
|
|
if ref == "" && strings.HasPrefix(suffix, "@") {
|
|
ref = "HEAD"
|
|
}
|
|
if !ValidateRef(ref, glob) {
|
|
return "", "", false
|
|
}
|
|
|
|
return ref, suffix, true
|
|
}
|