forked from pachyderm/pachyderm
/
ancestry.go
92 lines (82 loc) · 2.74 KB
/
ancestry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package ancestry
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// Parse parses s for git ancestry references.
// It supports special characters ^ ~, both of which are supported by git.
// Note in git ^ and ~ have different meanings on commits that have multiple
// parent commits. In Pachyderm there's only 1 parent possible so they have
// identical meanings. We support both simply for familiarity sake.
// In addition we support referencing the beginning of the branch with the . character.
// ParseAncestry returns the base reference and how many ancestors back to go.
// For example:
// foo^ -> foo, 1
// foo^^ -> foo, 2
// foo^3 -> foo, 3
// foo.1 -> foo, -1
// foo.3 -> foo, -3
// (all examples apply with ~ in place of ^ as well
func Parse(s string) (string, int, error) {
sepIndex := strings.IndexAny(s, "^~.")
if sepIndex == -1 {
return s, 0, nil
}
// Find the separator, which is either "^" or "~"
sep := s[sepIndex]
strAfterSep := s[sepIndex+1:]
// Try convert the string after the separator to an int.
intAfterSep, err := strconv.Atoi(strAfterSep)
// If it works, return
if err == nil {
if sep == '.' {
return s[:sepIndex], -1 * intAfterSep, nil
}
return s[:sepIndex], intAfterSep, nil
}
// Otherwise, we check if there's a sequence of separators, as in
// "master^^^^" or "master~~~~"
for i := sepIndex + 1; i < len(s); i++ {
if s[i] != sep {
// If we find a character that's not the separator, as in
// "master~whatever", then we return an error
return "", 0, fmt.Errorf("invalid ancestry syntax %q, cannot mix %c and %c characters", s, sep, s[i])
}
}
// Here we've confirmed that the commit ID ends with a sequence of
// (the same) separators and therefore uses the correct ancestry
// syntax.
if sep == '.' {
return s[:sepIndex], -1*len(s) - sepIndex, nil
}
return s[:sepIndex], len(s) - sepIndex, nil
}
// Add adds an ancestry reference to the given string.
func Add(s string, ancestors int) string {
if ancestors > 0 {
return fmt.Sprintf("%s~%d", s, ancestors)
} else if ancestors < 0 {
return fmt.Sprintf("%s.%d", s, ancestors*-1)
}
return s
}
var (
valid = regexp.MustCompile("^[a-zA-Z0-9_-]+$") // Matches a valid name
invalid = regexp.MustCompile("[^a-zA-Z0-9_-]") // matches an invalid character
repl = []byte("_")
)
// ValidateName validates a name to make sure that it can be used unambiguously
// with Ancestry syntax.
func ValidateName(name string) error {
if !valid.MatchString(name) {
return fmt.Errorf("name (%v) invalid: only alphanumeric characters, underscores, and dashes are allowed", name)
}
return nil
}
// SanitizeName forces a name to pass ValidateName, by replacing offending
// characters with _s
func SanitizeName(name string) string {
return invalid.ReplaceAllString(name, "_")
}