eclipse/lib/gitcache/gitrevisions.go

164 lines
4.2 KiB
Go
Raw Normal View History

2024-01-29 02:19:58 +00:00
// 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
}