github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/doltdb/anscestor_spec.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package doltdb 16 17 import ( 18 "errors" 19 "strconv" 20 "strings" 21 ) 22 23 func isDigit(b byte) bool { 24 return b >= byte('0') && b <= byte('9') 25 } 26 27 func parseInstructions(aSpec string) ([]int, error) { 28 instructions := make([]int, 0) 29 30 for i := 0; i < len(aSpec); i++ { 31 currInst := aSpec[i] 32 33 start := i 34 for i+1 < len(aSpec) && isDigit(aSpec[i+1]) { 35 i++ 36 } 37 38 num := 1 39 40 if start != i { 41 var err error 42 numStr := aSpec[start+1 : i+1] 43 num, err = strconv.Atoi(numStr) 44 45 if err != nil { 46 return nil, err 47 } 48 } 49 50 switch currInst { 51 case '^': 52 instructions = append(instructions, num-1) 53 case '~': 54 for j := 0; j < num; j++ { 55 instructions = append(instructions, 0) 56 } 57 default: 58 return nil, errors.New("Invalid HEAD spec: " + aSpec) 59 } 60 } 61 62 return instructions, nil 63 } 64 65 var emptyASpec = &AncestorSpec{"", []int{}} 66 67 // AncestorSpec supports using ^, ^N, and ~N together to specify an ancestor of a commit. 68 // ^ after a commit spec means the first parent of that commit. ^<n> means the <n>th parent (i.e. <rev>^ is equivalent 69 // to <rev>^1). As a special rule. 70 // ~<n> after a commit spec means the commit object that is the <n>th generation grand-parent of the named commit 71 // object, following only the first parents. I.e. <rev>~3 is equivalent to <rev>^^^ which is equivalent to 72 // <rev>^1^1^1. See below for an illustration of the usage of this form. 73 type AncestorSpec struct { 74 75 // SpecStr is string representation of the AncestorSpec 76 SpecStr string 77 78 // Instructions is a slice of parent indices. As you walk up the ancestor tree the first instruction is the index of 79 // the parent that should be used. The second index is the index of that parents parent that should be used. etc. 80 // When you've exhausted the instructions you've reached the referenced commit. 81 Instructions []int 82 } 83 84 // NewAncestorSpec takes an input string and validates it and converts it to a set of instructions used in walking up 85 // the ancestor tree 86 func NewAncestorSpec(s string) (*AncestorSpec, error) { 87 if s == "" { 88 return emptyASpec, nil 89 } 90 91 inst, err := parseInstructions(s) 92 93 if err != nil { 94 return nil, err 95 } 96 97 return &AncestorSpec{s, inst}, nil 98 } 99 100 // SplitAncestorSpec takes a string that is a commit spec suffixed with an ancestor spec, and splits them apart. 101 // If there is no ancestor spec then only the commit spec will be returned and the ancestorSpec will have no empty. 102 func SplitAncestorSpec(s string) (string, *AncestorSpec, error) { 103 cleanStr := strings.TrimSpace(s) 104 105 cIdx := strings.IndexByte(cleanStr, '^') 106 tIdx := strings.IndexByte(cleanStr, '~') 107 108 if cIdx == -1 && tIdx == -1 { 109 return cleanStr, emptyASpec, nil 110 } 111 112 idx := cIdx 113 if cIdx == -1 || (tIdx != -1 && tIdx < cIdx) { 114 idx = tIdx 115 } 116 117 commitSpec := cleanStr[:idx] 118 as, err := NewAncestorSpec(s[idx:]) 119 120 if err != nil { 121 return "", emptyASpec, err 122 } 123 124 return commitSpec, as, nil 125 }