// Copyright 2019 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package doltdb import ( "regexp" "strings" "github.com/dolthub/dolt/go/libraries/doltcore/ref" ) var hashRegex = regexp.MustCompile(`^[0-9a-v]{32}$`) const head string = "head" // IsValidUserBranchName returns true if name isn't a valid commit hash, it is not named "head" and // it matches the regular expression `[0-9a-z]+[-_0-9a-z]*[0-9a-z]+$` func IsValidUserBranchName(name string) bool { return name != head && !hashRegex.MatchString(name) && ref.IsValidBranchName(name) } // IsValidBranchRef validates that a BranchRef doesn't violate naming constraints. func IsValidBranchRef(dref ref.DoltRef) bool { return dref.GetType() == ref.BranchRefType && IsValidUserBranchName(dref.GetPath()) } // IsValidTagRef validates that a TagRef doesn't violate naming constraints. func IsValidTagRef(dref ref.DoltRef) bool { s := dref.GetPath() return dref.GetType() == ref.TagRefType && s != head && !hashRegex.MatchString(s) && ref.IsValidTagName(s) } func IsValidCommitHash(s string) bool { return hashRegex.MatchString(s) } type commitSpecType string const ( refCommitSpec commitSpecType = "ref" hashCommitSpec commitSpecType = "hash" headCommitSpec commitSpecType = "head" ) // CommitSpec handles three different types of string representations of commits. Commits can either be represented // by the hash of the commit, a branch name, or using "head" to represent the latest commit of the current branch. // An Ancestor spec can be appended to the end of any of these in order to reach commits that are in the ancestor tree // of the referenced commit. type CommitSpec struct { baseSpec string csType commitSpecType aSpec *AncestorSpec } // NewCommitSpec parses a string specifying a commit using dolt commit spec // syntax and returns a |*CommitSpec|. A commit spec has a base commit and an // optional ancestor specification. The syntax admits three types of base // commit references: // * head -- the literal string HEAD specifies the HEAD reference of the // current working set. // * a commit hash, like 46m0aqr8c1vuv76ml33cdtr8722hsbhn -- a fully specified // commit hash. // * a ref -- referring to a branch or tag reference in the current dolt database. // Examples of branch refs include `master`, `heads/master`, `refs/heads/master`, // `origin/master`, `refs/remotes/origin/master`. // Examples of tag refs include `v1.0`, `tags/v1.0`, `refs/tags/v1.0`, // `origin/v1.0`, `refs/remotes/origin/v1.0`. // // A commit spec has an optional ancestor specification, which describes a // traversal of commit parents, starting at the base commit, in order to arrive // at the actually specified commit. See |AncestorSpec|. Examples of // |CommitSpec|s: // * HEAD // * master // * HEAD~ // * remotes/origin/master~~ // * refs/heads/my-feature-branch^2~ // // Constructing a |CommitSpec| does not mean the specified branch or commit // exists. This carries a description of how to find the specified commit. See // |doltdb.Resolve| for resolving a |CommitSpec| to a |Commit|. func NewCommitSpec(cSpecStr string) (*CommitSpec, error) { cSpecStrLwr := strings.TrimSpace(cSpecStr) name, as, err := SplitAncestorSpec(cSpecStrLwr) if err != nil { return nil, err } if strings.EqualFold(name, head) { return &CommitSpec{head, headCommitSpec, as}, nil } if hashRegex.MatchString(name) { return &CommitSpec{name, hashCommitSpec, as}, nil } if !ref.IsValidBranchName(name) { return nil, ErrInvalidBranchOrHash } return &CommitSpec{name, refCommitSpec, as}, nil }