github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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/main 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 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 GetRemRefToLocal() branchMapper 52 } 53 54 // ParseRefSpec parses a RefSpec from a string. 55 func ParseRefSpec(refSpecStr string) (RefSpec, error) { 56 return ParseRefSpecForRemote("", refSpecStr) 57 } 58 59 // ParseRefSpecForRemote takes the name of a remote and a refspec, and parses that refspec, verifying that it refers 60 // to the appropriate remote 61 func ParseRefSpecForRemote(remote, refSpecStr string) (RefSpec, error) { 62 var fromRef DoltRef 63 var toRef DoltRef 64 var err error 65 if len(refSpecStr) == 0 { 66 return nil, ErrInvalidRefSpec 67 } else if refSpecStr[0] == ':' { 68 fromRef = EmptyBranchRef 69 toRef, err = Parse(refSpecStr[1:]) 70 71 if err != nil { 72 return nil, ErrInvalidRefSpec 73 } 74 } else { 75 tokens := strings.Split(refSpecStr, ":") 76 77 if len(tokens) == 0 { 78 return nil, ErrInvalidRefSpec 79 } 80 81 srcStr := tokens[0] 82 destStr := tokens[0] 83 84 if len(tokens) > 2 { 85 return nil, ErrInvalidRefSpec 86 } else if len(tokens) == 2 { 87 destStr = tokens[1] 88 } 89 90 fromRef, err = Parse(srcStr) 91 92 if err != nil { 93 return nil, ErrInvalidRefSpec 94 } 95 96 toRef, err = Parse(destStr) 97 98 if err != nil { 99 return nil, ErrInvalidRefSpec 100 } 101 } 102 103 if fromRef.GetType() == BranchRefType && toRef.GetType() == RemoteRefType { 104 return NewLocalToRemoteTrackingRef(remote, fromRef.(BranchRef), toRef.(RemoteRef)) 105 } else if fromRef.GetType() == BranchRefType && toRef.GetType() == BranchRefType { 106 return NewBranchToBranchRefSpec(fromRef.(BranchRef), toRef.(BranchRef)) 107 } else if fromRef.GetType() == TagRefType && toRef.GetType() == TagRefType { 108 return NewTagToTagRefSpec(fromRef.(TagRef), toRef.(TagRef)) 109 } 110 111 return nil, ErrUnsupportedMapping 112 } 113 114 type branchMapper interface { 115 mapBranch(string) string 116 } 117 118 type identityBranchMapper string 119 120 func (ibm identityBranchMapper) mapBranch(s string) string { 121 return string(ibm) 122 } 123 124 type wcBranchMapper struct { 125 prefix string 126 suffix string 127 } 128 129 func newWildcardBranchMapper(s string) wcBranchMapper { 130 tokens := strings.Split(s, "*") 131 132 if len(tokens) != 2 { 133 panic("invalid localPattern") 134 } 135 136 return wcBranchMapper{tokens[0], tokens[1]} 137 } 138 139 func (wcbm wcBranchMapper) mapBranch(s string) string { 140 return wcbm.prefix + s + wcbm.suffix 141 } 142 143 // BranchToBranchRefSpec maps one branch to another. 144 type BranchToBranchRefSpec struct { 145 srcRef DoltRef 146 destRef DoltRef 147 } 148 149 // NewBranchToBranchRefSpec takes a source and destination BranchRef and returns a RefSpec that maps source to dest. 150 func NewBranchToBranchRefSpec(srcRef, destRef BranchRef) (RefSpec, error) { 151 return BranchToBranchRefSpec{ 152 srcRef: srcRef, 153 destRef: destRef, 154 }, nil 155 } 156 157 // SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef 158 func (rs BranchToBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef { 159 return rs.srcRef 160 } 161 162 // DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or 163 // nil if it does not match the local pattern. 164 func (rs BranchToBranchRefSpec) DestRef(r DoltRef) DoltRef { 165 if Equals(r, rs.srcRef) { 166 return rs.destRef 167 } 168 169 return nil 170 } 171 172 type TagToTagRefSpec struct { 173 srcRef DoltRef 174 destRef DoltRef 175 } 176 177 // NewTagToTagRefSpec takes a source and destination TagRef and returns a RefSpec that maps source to dest. 178 func NewTagToTagRefSpec(srcRef, destRef TagRef) (RefSpec, error) { 179 return TagToTagRefSpec{ 180 srcRef: srcRef, 181 destRef: destRef, 182 }, nil 183 } 184 185 // SrcRef will always determine the DoltRef specified as the source ref regardless to the cwbRef 186 func (rs TagToTagRefSpec) SrcRef(_ DoltRef) DoltRef { 187 return rs.srcRef 188 } 189 190 // DestRef verifies the localRef matches the refspecs local pattern, and then maps it to a remote tracking branch, or 191 // nil if it does not match the local pattern. 192 func (rs TagToTagRefSpec) DestRef(r DoltRef) DoltRef { 193 if Equals(r, rs.srcRef) { 194 return rs.destRef 195 } 196 197 return nil 198 } 199 200 // BranchToTrackingBranchRefSpec maps a branch to the branch that should be tracking it 201 type BranchToTrackingBranchRefSpec struct { 202 localPattern pattern 203 remPattern pattern 204 remote string 205 localToRemRef branchMapper 206 remRefToLocal branchMapper 207 } 208 209 func NewLocalToRemoteTrackingRef(remote string, srcRef BranchRef, destRef RemoteRef) (RefSpec, error) { 210 srcWCs := strings.Count(srcRef.GetPath(), "*") 211 destWCs := strings.Count(destRef.GetPath(), "*") 212 213 if srcWCs != destWCs || srcWCs > 1 { 214 return nil, ErrInvalidRefSpec 215 } else { 216 remoteInRef, ok := strhelp.NthToken(destRef.GetPath(), '/', 0) 217 218 if !ok { 219 return nil, ErrInvalidRefSpec 220 } else if remote != "" && remote != remoteInRef { 221 return nil, ErrInvalidRefSpec 222 } 223 224 if srcWCs == 0 { 225 srcPattern := strPattern(srcRef.GetPath()) 226 destPattern := strPattern(destRef.GetPath()) 227 srcToDestMapper := identityBranchMapper(destRef.GetPath()[len(remoteInRef)+1:]) 228 destToSrcMapper := identityBranchMapper(srcRef.GetPath()) 229 230 return BranchToTrackingBranchRefSpec{ 231 localPattern: srcPattern, 232 remPattern: destPattern, 233 remote: remoteInRef, 234 localToRemRef: srcToDestMapper, 235 remRefToLocal: destToSrcMapper, 236 }, nil 237 } else { 238 srcPattern := newWildcardPattern(srcRef.GetPath()) 239 destPattern := newWildcardPattern(destRef.GetPath()) 240 srcToDestMapper := newWildcardBranchMapper(destRef.GetPath()[len(remoteInRef)+1:]) 241 destToSrcMapper := newWildcardBranchMapper(srcRef.GetPath()) 242 243 return BranchToTrackingBranchRefSpec{ 244 localPattern: srcPattern, 245 remPattern: destPattern, 246 remote: remoteInRef, 247 localToRemRef: srcToDestMapper, 248 remRefToLocal: destToSrcMapper, 249 }, nil 250 } 251 } 252 } 253 254 // SrcRef will return the current working branch reference that is passed in as long as the cwbRef matches 255 // the source portion of the ref spec 256 func (rs BranchToTrackingBranchRefSpec) SrcRef(cwbRef DoltRef) DoltRef { 257 if cwbRef.GetType() == BranchRefType { 258 _, matches := rs.localPattern.matches(cwbRef.GetPath()) 259 if matches { 260 return cwbRef 261 } 262 } 263 264 return nil 265 } 266 267 // DestRef verifies the branchRef matches the refspec's local pattern, and then maps it to a remote tracking branch, or 268 // to nil if it does not match the pattern. 269 func (rs BranchToTrackingBranchRefSpec) DestRef(branchRef DoltRef) DoltRef { 270 if branchRef.GetType() == BranchRefType { 271 captured, matches := rs.localPattern.matches(branchRef.GetPath()) 272 if matches { 273 return NewRemoteRef(rs.remote, rs.localToRemRef.mapBranch(captured)) 274 } 275 } 276 277 return nil 278 } 279 280 // GetRemote returns the name of the remote being operated on. 281 func (rs BranchToTrackingBranchRefSpec) GetRemote() string { 282 return rs.remote 283 } 284 285 // GetRemRefToLocal returns the local tracking branch. 286 func (rs BranchToTrackingBranchRefSpec) GetRemRefToLocal() branchMapper { 287 return rs.remRefToLocal 288 }