299 lines
8.8 KiB
Go
299 lines
8.8 KiB
Go
// 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 ref
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/dolthub/dolt/go/libraries/utils/strhelp"
|
|
)
|
|
|
|
// ErrInvalidRefSpec is the error returned when a refspec isn't syntactically valid
|
|
var ErrInvalidRefSpec = errors.New("invalid ref spec")
|
|
|
|
// ErrInvalidMapping is the error returned when a refspec tries to do an invalid mapping, such as mapping
|
|
// refs/heads/main to refs/remotes/origin/*
|
|
var ErrInvalidMapping = errors.New("invalid ref spec mapping")
|
|
|
|
// ErrUnsupportedMapping is returned when trying to do anything other than map local branches (refs/heads/*) to
|
|
// remote tracking branches (refs/remotes/*). As other mappings are added this code will need updating
|
|
var ErrUnsupportedMapping = errors.New("unsupported mapping")
|
|
|
|
// RefSpec is an interface for mapping a reference in one space to a reference in another space.
|
|
type RefSpec interface {
|
|
// SrcRef will take a reference to the current working branch and return a reference to what should be used
|
|
// for the source reference of an operation involving a reference spec
|
|
SrcRef(cwbRef DoltRef) DoltRef
|
|
|
|
// DestRef will take a source reference and return a reference to what should be used for the destination
|
|
// reference of an operation involving a reference spec.
|
|
DestRef(srcRef DoltRef) DoltRef
|
|
}
|
|
|
|
// RemoteRefSpec is an interface that embeds the RefSpec interface and provides an additional method to get the name
|
|
// of a remote.
|
|
type RemoteRefSpec interface {
|
|
RefSpec
|
|
GetRemote() string
|
|
GetRemRefToLocal() branchMapper
|
|
}
|
|
|
|
// ParseRefSpec parses a RefSpec from a string.
|
|
func ParseRefSpec(refSpecStr string) (RefSpec, error) {
|
|
return ParseRefSpecForRemote("", refSpecStr)
|
|
}
|
|
|
|
// ParseRefSpecForRemote takes the name of a remote and a refspec, and parses that refspec, verifying that it refers
|
|
// to the appropriate remote
|
|
func ParseRefSpecForRemote(remote, refSpecStr string) (RefSpec, error) {
|
|
var fromRef DoltRef
|
|
var toRef DoltRef
|
|
var err error
|
|
if len(refSpecStr) == 0 {
|
|
return nil, ErrInvalidRefSpec
|
|
} else if refSpecStr[0] == ':' {
|
|
fromRef = EmptyBranchRef
|
|
toRef, err = Parse(refSpecStr[1:])
|
|
|
|
if err != nil {
|
|
return nil, ErrInvalidRefSpec
|
|
}
|
|
} else {
|
|
tokens := strings.Split(refSpecStr, ":")
|
|
|
|
if len(tokens) == 0 {
|
|
return nil, ErrInvalidRefSpec
|
|
}
|
|
|
|
srcStr := tokens[0]
|
|
destStr := tokens[0]
|
|
|
|
if len(tokens) > 2 {
|
|
return nil, ErrInvalidRefSpec
|
|
} else if len(tokens) == 2 {
|
|
destStr = tokens[1]
|
|
}
|
|
|
|
fromRef, err = Parse(srcStr)
|
|
|
|
if err != nil {
|
|
return nil, ErrInvalidRefSpec
|
|
}
|
|
|
|
toRef, err = Parse(destStr)
|
|
|
|
if err != nil {
|
|
return nil, ErrInvalidRefSpec
|
|
}
|
|
}
|
|
|
|
if fromRef.GetType() == BranchRefType && toRef.GetType() == RemoteRefType {
|
|
return NewLocalToRemoteTrackingRef(remote, fromRef.(BranchRef), toRef.(RemoteRef))
|
|
} else if fromRef.GetType() == BranchRefType && toRef.GetType() == BranchRefType {
|
|
return NewBranchToBranchRefSpec(fromRef.(BranchRef), toRef.(BranchRef))
|
|
} else if fromRef.GetType() == TagRefType && toRef.GetType() == TagRefType {
|
|
return NewTagToTagRefSpec(fromRef.(TagRef), toRef.(TagRef))
|
|
}
|
|
|
|
return nil, ErrUnsupportedMapping
|
|
}
|
|
|
|
type branchMapper interface {
|
|
mapBranch(string) string
|
|
}
|
|
|
|
type identityBranchMapper string
|
|
|
|
func (ibm identityBranchMapper) mapBranch(s string) string {
|
|
return string(ibm)
|
|
}
|
|
|
|
type wcBranchMapper struct {
|
|
prefix string
|
|
suffix string
|
|
}
|
|
|
|
func newWildcardBranchMapper(s string) wcBranchMapper {
|
|
tokens := strings.Split(s, "*")
|
|
|
|
if len(tokens) != 2 {
|
|
panic("invalid localPattern")
|
|
}
|
|
|
|
return wcBranchMapper{tokens[0], tokens[1]}
|
|
}
|
|
|
|
func (wcbm wcBranchMapper) mapBranch(s string) string {
|
|
return wcbm.prefix + s + wcbm.suffix
|
|
}
|
|
|
|
// BranchToBranchRefSpec maps one branch to another.
|
|
type BranchToBranchRefSpec struct {
|
|
srcRef DoltRef
|
|
destRef DoltRef
|
|
}
|
|
|
|
// NewBranchToBranchRefSpec takes a source and destination BranchRef and returns a RefSpec that maps source to dest.
|
|
func NewBranchToBranchRefSpec(srcRef, destRef BranchRef) (RefSpec, error) {
|
|
return BranchToBranchRefSpec{
|
|
srcRef: srcRef,
|
|
destRef: destRef,
|
|
}, nil
|
|
}
|
|
|
|
// SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef
|
|
func (rs BranchToBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef {
|
|
return rs.srcRef
|
|
}
|
|
|
|
// DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or
|
|
// nil if it does not match the local pattern.
|
|
func (rs BranchToBranchRefSpec) DestRef(r DoltRef) DoltRef {
|
|
if Equals(r, rs.srcRef) {
|
|
return rs.destRef
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type TagToTagRefSpec struct {
|
|
srcRef DoltRef
|
|
destRef DoltRef
|
|
}
|
|
|
|
// NewTagToTagRefSpec takes a source and destination TagRef and returns a RefSpec that maps source to dest.
|
|
func NewTagToTagRefSpec(srcRef, destRef TagRef) (RefSpec, error) {
|
|
return TagToTagRefSpec{
|
|
srcRef: srcRef,
|
|
destRef: destRef,
|
|
}, nil
|
|
}
|
|
|
|
// SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef
|
|
func (rs TagToTagRefSpec) SrcRef(_ DoltRef) DoltRef {
|
|
return rs.srcRef
|
|
}
|
|
|
|
// DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or
|
|
// nil if it does not match the local pattern.
|
|
func (rs TagToTagRefSpec) DestRef(r DoltRef) DoltRef {
|
|
if Equals(r, rs.srcRef) {
|
|
return rs.destRef
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRemote returns the name of the remote being operated on.
|
|
func (rs TagToTagRefSpec) GetRemote() string {
|
|
return ""
|
|
}
|
|
|
|
// GetRemRefToLocal returns the local tracking branch.
|
|
func (rs TagToTagRefSpec) GetRemRefToLocal() branchMapper {
|
|
return identityBranchMapper(rs.srcRef.GetPath())
|
|
}
|
|
|
|
// BranchToTrackingBranchRefSpec maps a branch to the branch that should be tracking it
|
|
type BranchToTrackingBranchRefSpec struct {
|
|
localPattern pattern
|
|
remPattern pattern
|
|
remote string
|
|
localToRemRef branchMapper
|
|
remRefToLocal branchMapper
|
|
}
|
|
|
|
func NewLocalToRemoteTrackingRef(remote string, srcRef BranchRef, destRef RemoteRef) (RefSpec, error) {
|
|
srcWCs := strings.Count(srcRef.GetPath(), "*")
|
|
destWCs := strings.Count(destRef.GetPath(), "*")
|
|
|
|
if srcWCs != destWCs || srcWCs > 1 {
|
|
return nil, ErrInvalidRefSpec
|
|
} else {
|
|
remoteInRef, ok := strhelp.NthToken(destRef.GetPath(), '/', 0)
|
|
|
|
if !ok {
|
|
return nil, ErrInvalidRefSpec
|
|
} else if remote != "" && remote != remoteInRef {
|
|
return nil, ErrInvalidRefSpec
|
|
}
|
|
|
|
if srcWCs == 0 {
|
|
srcPattern := strPattern(srcRef.GetPath())
|
|
destPattern := strPattern(destRef.GetPath())
|
|
srcToDestMapper := identityBranchMapper(destRef.GetPath()[len(remoteInRef)+1:])
|
|
destToSrcMapper := identityBranchMapper(srcRef.GetPath())
|
|
|
|
return BranchToTrackingBranchRefSpec{
|
|
localPattern: srcPattern,
|
|
remPattern: destPattern,
|
|
remote: remoteInRef,
|
|
localToRemRef: srcToDestMapper,
|
|
remRefToLocal: destToSrcMapper,
|
|
}, nil
|
|
} else {
|
|
srcPattern := newWildcardPattern(srcRef.GetPath())
|
|
destPattern := newWildcardPattern(destRef.GetPath())
|
|
srcToDestMapper := newWildcardBranchMapper(destRef.GetPath()[len(remoteInRef)+1:])
|
|
destToSrcMapper := newWildcardBranchMapper(srcRef.GetPath())
|
|
|
|
return BranchToTrackingBranchRefSpec{
|
|
localPattern: srcPattern,
|
|
remPattern: destPattern,
|
|
remote: remoteInRef,
|
|
localToRemRef: srcToDestMapper,
|
|
remRefToLocal: destToSrcMapper,
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// SrcRef will return the current working branch reference that is passed in as long as the cwbRef matches
|
|
// the source portion of the ref spec
|
|
func (rs BranchToTrackingBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef {
|
|
if cwbRef.GetType() == BranchRefType {
|
|
_, matches := rs.localPattern.matches(cwbRef.GetPath())
|
|
if matches {
|
|
return cwbRef
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DestRef verifies the branchRef matches the refspec's local pattern, and then maps it to a remote tracking branch, or
|
|
// to nil if it does not match the pattern.
|
|
func (rs BranchToTrackingBranchRefSpec) DestRef(branchRef DoltRef) DoltRef {
|
|
if branchRef.GetType() == BranchRefType {
|
|
captured, matches := rs.localPattern.matches(branchRef.GetPath())
|
|
if matches {
|
|
return NewRemoteRef(rs.remote, rs.localToRemRef.mapBranch(captured))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRemote returns the name of the remote being operated on.
|
|
func (rs BranchToTrackingBranchRefSpec) GetRemote() string {
|
|
return rs.remote
|
|
}
|
|
|
|
// GetRemRefToLocal returns the local tracking branch.
|
|
func (rs BranchToTrackingBranchRefSpec) GetRemRefToLocal() branchMapper {
|
|
return rs.remRefToLocal
|
|
}
|