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 }