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  }