github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/commit_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  	"regexp"
    19  	"strings"
    20  
    21  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    22  )
    23  
    24  var hashRegex = regexp.MustCompile(`^[0-9a-v]{32}$`)
    25  
    26  const head string = "head"
    27  
    28  // IsValidUserBranchName returns true if name isn't a valid commit hash, it is not named "head" and
    29  // it matches the regular expression `[0-9a-z]+[-_0-9a-z]*[0-9a-z]+$`
    30  func IsValidUserBranchName(name string) bool {
    31  	return name != head && !hashRegex.MatchString(name) && ref.IsValidBranchName(name)
    32  }
    33  
    34  // IsValidBranchRef validates that a BranchRef doesn't violate naming constraints.
    35  func IsValidBranchRef(dref ref.DoltRef) bool {
    36  	return dref.GetType() == ref.BranchRefType && IsValidUserBranchName(dref.GetPath())
    37  }
    38  
    39  // IsValidTagRef validates that a TagRef doesn't violate naming constraints.
    40  func IsValidTagRef(dref ref.DoltRef) bool {
    41  	s := dref.GetPath()
    42  	return dref.GetType() == ref.TagRefType &&
    43  		s != head &&
    44  		!hashRegex.MatchString(s) &&
    45  		ref.IsValidTagName(s)
    46  }
    47  
    48  func IsValidCommitHash(s string) bool {
    49  	return hashRegex.MatchString(s)
    50  }
    51  
    52  type commitSpecType string
    53  
    54  const (
    55  	refCommitSpec  commitSpecType = "ref"
    56  	hashCommitSpec commitSpecType = "hash"
    57  	headCommitSpec commitSpecType = "head"
    58  )
    59  
    60  // CommitSpec handles three different types of string representations of commits.  Commits can either be represented
    61  // by the hash of the commit, a branch name, or using "head" to represent the latest commit of the current branch.
    62  // An Ancestor spec can be appended to the end of any of these in order to reach commits that are in the ancestor tree
    63  // of the referenced commit.
    64  type CommitSpec struct {
    65  	baseSpec string
    66  	csType   commitSpecType
    67  	aSpec    *AncestorSpec
    68  }
    69  
    70  // NewCommitSpec parses a string specifying a commit using dolt commit spec
    71  // syntax and returns a |*CommitSpec|. A commit spec has a base commit and an
    72  // optional ancestor specification. The syntax admits three types of base
    73  // commit references:
    74  // * head -- the literal string HEAD specifies the HEAD reference of the
    75  // current working set.
    76  // * a commit hash, like 46m0aqr8c1vuv76ml33cdtr8722hsbhn -- a fully specified
    77  // commit hash.
    78  // * a ref -- referring to a branch or tag reference in the current dolt database.
    79  // Examples of branch refs include `master`, `heads/master`, `refs/heads/master`,
    80  // `origin/master`, `refs/remotes/origin/master`.
    81  // Examples of tag refs include `v1.0`, `tags/v1.0`, `refs/tags/v1.0`,
    82  // `origin/v1.0`, `refs/remotes/origin/v1.0`.
    83  //
    84  // A commit spec has an optional ancestor specification, which describes a
    85  // traversal of commit parents, starting at the base commit, in order to arrive
    86  // at the actually specified commit. See |AncestorSpec|. Examples of
    87  // |CommitSpec|s:
    88  // * HEAD
    89  // * master
    90  // * HEAD~
    91  // * remotes/origin/master~~
    92  // * refs/heads/my-feature-branch^2~
    93  //
    94  // Constructing a |CommitSpec| does not mean the specified branch or commit
    95  // exists. This carries a description of how to find the specified commit. See
    96  // |doltdb.Resolve| for resolving a |CommitSpec| to a |Commit|.
    97  func NewCommitSpec(cSpecStr string) (*CommitSpec, error) {
    98  	cSpecStrLwr := strings.TrimSpace(cSpecStr)
    99  
   100  	name, as, err := SplitAncestorSpec(cSpecStrLwr)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	if strings.ToLower(name) == head {
   106  		return &CommitSpec{head, headCommitSpec, as}, nil
   107  	}
   108  	if hashRegex.MatchString(name) {
   109  		return &CommitSpec{name, hashCommitSpec, as}, nil
   110  	}
   111  	if !ref.IsValidBranchName(name) {
   112  		return nil, ErrInvalidBranchOrHash
   113  	}
   114  	return &CommitSpec{name, refCommitSpec, as}, nil
   115  }