github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/ref/ref_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 ref
    16  
    17  import (
    18  	"errors"
    19  	"strings"
    20  
    21  	"github.com/dolthub/dolt/go/libraries/utils/strhelp"
    22  )
    23  
    24  // ErrInvalidRefSpec is the error returned when a refspec isn't syntactically valid
    25  var ErrInvalidRefSpec = errors.New("invalid ref spec")
    26  
    27  // ErrInvalidMapping is the error returned when a refspec tries to do an invalid mapping, such as mapping
    28  // refs/heads/master to refs/remotes/origin/*
    29  var ErrInvalidMapping = errors.New("invalid ref spec mapping")
    30  
    31  // ErrUnsupportedMapping is returned when trying to do anything other than map local branches (refs/heads/*) to
    32  // remote tracking branches (refs/remotes/*).  As other mappings are added this code will need updating
    33  var ErrUnsupportedMapping = errors.New("unsupported mapping")
    34  
    35  // RefSpec is an interface for mapping a reference in one space to a reference in another space.
    36  type RefSpec interface {
    37  	// SrcRef will take a reference to the current working branch and return a reference to what should be used
    38  	// for the source reference of an operation involving a reference spec
    39  	SrcRef(cwbRef DoltRef) DoltRef
    40  
    41  	// DestRef will take a source reference and return a reference to what shat should be used for the destination
    42  	// reference of an operation involving a reference spec.
    43  	DestRef(srcRef DoltRef) DoltRef
    44  }
    45  
    46  // RemoteRefSpec is an interface that embeds the RefSpec interface and provides an additional method to get the name
    47  // of a remote.
    48  type RemoteRefSpec interface {
    49  	RefSpec
    50  	GetRemote() string
    51  }
    52  
    53  // ParseRefSpec parses a RefSpec from a string.
    54  func ParseRefSpec(refSpecStr string) (RefSpec, error) {
    55  	return ParseRefSpecForRemote("", refSpecStr)
    56  }
    57  
    58  // ParseRefSpecForRemote takes the name of a remote and a refspec, and parses that refspec, verifying that it refers
    59  // to the appropriate remote
    60  func ParseRefSpecForRemote(remote, refSpecStr string) (RefSpec, error) {
    61  	var fromRef DoltRef
    62  	var toRef DoltRef
    63  	var err error
    64  	if refSpecStr[0] == ':' {
    65  		fromRef = EmptyBranchRef
    66  		toRef, err = Parse(refSpecStr[1:])
    67  
    68  		if err != nil {
    69  			return nil, ErrInvalidRefSpec
    70  		}
    71  	} else {
    72  		tokens := strings.Split(refSpecStr, ":")
    73  
    74  		if len(tokens) == 0 {
    75  			return nil, ErrInvalidRefSpec
    76  		}
    77  
    78  		srcStr := tokens[0]
    79  		destStr := tokens[0]
    80  
    81  		if len(tokens) > 2 {
    82  			return nil, ErrInvalidRefSpec
    83  		} else if len(tokens) == 2 {
    84  			destStr = tokens[1]
    85  		}
    86  
    87  		fromRef, err = Parse(srcStr)
    88  
    89  		if err != nil {
    90  			return nil, ErrInvalidRefSpec
    91  		}
    92  
    93  		toRef, err = Parse(destStr)
    94  
    95  		if err != nil {
    96  			return nil, ErrInvalidRefSpec
    97  		}
    98  	}
    99  
   100  	if fromRef.GetType() == BranchRefType && toRef.GetType() == RemoteRefType {
   101  		return newLocalToRemoteTrackingRef(remote, fromRef.(BranchRef), toRef.(RemoteRef))
   102  	} else if fromRef.GetType() == BranchRefType && toRef.GetType() == BranchRefType {
   103  		return NewBranchToBranchRefSpec(fromRef.(BranchRef), toRef.(BranchRef))
   104  	} else if fromRef.GetType() == TagRefType && toRef.GetType() == TagRefType {
   105  		return NewTagToTagRefSpec(fromRef.(TagRef), toRef.(TagRef))
   106  	}
   107  
   108  	return nil, ErrUnsupportedMapping
   109  }
   110  
   111  type branchMapper interface {
   112  	mapBranch(string) string
   113  }
   114  
   115  type identityBranchMapper string
   116  
   117  func (ibm identityBranchMapper) mapBranch(s string) string {
   118  	return string(ibm)
   119  }
   120  
   121  type wcBranchMapper struct {
   122  	prefix string
   123  	suffix string
   124  }
   125  
   126  func newWildcardBranchMapper(s string) wcBranchMapper {
   127  	tokens := strings.Split(s, "*")
   128  
   129  	if len(tokens) != 2 {
   130  		panic("invalid localPattern")
   131  	}
   132  
   133  	return wcBranchMapper{tokens[0], tokens[1]}
   134  }
   135  
   136  func (wcbm wcBranchMapper) mapBranch(s string) string {
   137  	return wcbm.prefix + s + wcbm.suffix
   138  }
   139  
   140  // BranchToBranchRefSpec maps one branch to another.
   141  type BranchToBranchRefSpec struct {
   142  	srcRef  DoltRef
   143  	destRef DoltRef
   144  }
   145  
   146  // NewBranchToBranchRefSpec takes a source and destination BranchRef and returns a RefSpec that maps source to dest.
   147  func NewBranchToBranchRefSpec(srcRef, destRef BranchRef) (RefSpec, error) {
   148  	return BranchToBranchRefSpec{
   149  		srcRef:  srcRef,
   150  		destRef: destRef,
   151  	}, nil
   152  }
   153  
   154  // SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef
   155  func (rs BranchToBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef {
   156  	return rs.srcRef
   157  }
   158  
   159  // DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or
   160  // nil if it does not match the local pattern.
   161  func (rs BranchToBranchRefSpec) DestRef(r DoltRef) DoltRef {
   162  	if Equals(r, rs.srcRef) {
   163  		return rs.destRef
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  type TagToTagRefSpec struct {
   170  	srcRef  DoltRef
   171  	destRef DoltRef
   172  }
   173  
   174  // NewTagToTagRefSpec takes a source and destination TagRef and returns a RefSpec that maps source to dest.
   175  func NewTagToTagRefSpec(srcRef, destRef TagRef) (RefSpec, error) {
   176  	return TagToTagRefSpec{
   177  		srcRef:  srcRef,
   178  		destRef: destRef,
   179  	}, nil
   180  }
   181  
   182  // SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef
   183  func (rs TagToTagRefSpec) SrcRef(_ DoltRef) DoltRef {
   184  	return rs.srcRef
   185  }
   186  
   187  // DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or
   188  // nil if it does not match the local pattern.
   189  func (rs TagToTagRefSpec) DestRef(r DoltRef) DoltRef {
   190  	if Equals(r, rs.srcRef) {
   191  		return rs.destRef
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  // BranchToTrackingBranchRefSpec maps a branch to the branch that should be tracking it
   198  type BranchToTrackingBranchRefSpec struct {
   199  	localPattern  pattern
   200  	remPattern    pattern
   201  	remote        string
   202  	localToRemRef branchMapper
   203  	remRefToLocal branchMapper
   204  }
   205  
   206  func newLocalToRemoteTrackingRef(remote string, srcRef BranchRef, destRef RemoteRef) (RefSpec, error) {
   207  	srcWCs := strings.Count(srcRef.GetPath(), "*")
   208  	destWCs := strings.Count(destRef.GetPath(), "*")
   209  
   210  	if srcWCs != destWCs || srcWCs > 1 {
   211  		return nil, ErrInvalidRefSpec
   212  	} else {
   213  		remoteInRef, ok := strhelp.NthToken(destRef.GetPath(), '/', 0)
   214  
   215  		if !ok {
   216  			return nil, ErrInvalidRefSpec
   217  		} else if remote != "" && remote != remoteInRef {
   218  			return nil, ErrInvalidRefSpec
   219  		}
   220  
   221  		if srcWCs == 0 {
   222  			srcPattern := strPattern(srcRef.GetPath())
   223  			destPattern := strPattern(destRef.GetPath())
   224  			srcToDestMapper := identityBranchMapper(destRef.GetPath()[len(remoteInRef):])
   225  			destToSrcMapper := identityBranchMapper(srcRef.GetPath())
   226  
   227  			return BranchToTrackingBranchRefSpec{
   228  				localPattern:  srcPattern,
   229  				remPattern:    destPattern,
   230  				remote:        remoteInRef,
   231  				localToRemRef: srcToDestMapper,
   232  				remRefToLocal: destToSrcMapper,
   233  			}, nil
   234  		} else {
   235  			srcPattern := newWildcardPattern(srcRef.GetPath())
   236  			destPattern := newWildcardPattern(destRef.GetPath())
   237  			srcToDestMapper := newWildcardBranchMapper(destRef.GetPath()[len(remoteInRef)+1:])
   238  			destToSrcMapper := newWildcardBranchMapper(srcRef.GetPath())
   239  
   240  			return BranchToTrackingBranchRefSpec{
   241  				localPattern:  srcPattern,
   242  				remPattern:    destPattern,
   243  				remote:        remoteInRef,
   244  				localToRemRef: srcToDestMapper,
   245  				remRefToLocal: destToSrcMapper,
   246  			}, nil
   247  		}
   248  	}
   249  }
   250  
   251  // SrcRef will return the current working branchh reference that is passed in as long as the cwbRef matches
   252  // the source portion of the ref spec
   253  func (rs BranchToTrackingBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef {
   254  	if cwbRef.GetType() == BranchRefType {
   255  		_, matches := rs.localPattern.matches(cwbRef.GetPath())
   256  		if matches {
   257  			return cwbRef
   258  		}
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  // DestRef verifies the branchRef matches the refspec's local pattern, and then maps it to a remote tracking branch, or
   265  // to nil if it does not match the pattern.
   266  func (rs BranchToTrackingBranchRefSpec) DestRef(branchRef DoltRef) DoltRef {
   267  	if branchRef.GetType() == BranchRefType {
   268  		captured, matches := rs.localPattern.matches(branchRef.GetPath())
   269  		if matches {
   270  			return NewRemoteRef(rs.remote, rs.localToRemRef.mapBranch(captured))
   271  		}
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  // GetRemote returns the name of the remote being operated on.
   278  func (rs BranchToTrackingBranchRefSpec) GetRemote() string {
   279  	return rs.remote
   280  }