1 /* 2 * Copyright (C) 2008, 2013 Shawn O. Pearce <spearce@spearce.org> and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 11 package org.eclipse.jgit.transport; 12 13 import java.io.Serializable; 14 import java.text.MessageFormat; 15 import java.util.Objects; 16 17 import org.eclipse.jgit.internal.JGitText; 18 import org.eclipse.jgit.lib.Constants; 19 import org.eclipse.jgit.lib.Ref; 20 21 /** 22 * Describes how refs in one repository copy into another repository. 23 * <p> 24 * A ref specification provides matching support and limited rules to rewrite a 25 * reference in one repository to another reference in another repository. 26 */ 27 public class RefSpec implements Serializable { 28 private static final long serialVersionUID = 1L; 29 30 /** 31 * Suffix for wildcard ref spec component, that indicate matching all refs 32 * with specified prefix. 33 */ 34 public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$ 35 36 /** 37 * Check whether provided string is a wildcard ref spec component. 38 * 39 * @param s 40 * ref spec component - string to test. Can be null. 41 * @return true if provided string is a wildcard ref spec component. 42 */ 43 public static boolean isWildcard(String s) { 44 return s != null && s.contains("*"); //$NON-NLS-1$ 45 } 46 47 /** Does this specification ask for forced updated (rewind/reset)? */ 48 private boolean force; 49 50 /** Is this specification actually a wildcard match? */ 51 private boolean wildcard; 52 53 /** Is this the special ":" RefSpec? */ 54 private boolean matching; 55 56 /** 57 * How strict to be about wildcards. 58 * 59 * @since 4.5 60 */ 61 public enum WildcardMode { 62 /** 63 * Reject refspecs with an asterisk on the source side and not the 64 * destination side or vice versa. This is the mode used by FetchCommand 65 * and PushCommand to create a one-to-one mapping between source and 66 * destination refs. 67 */ 68 REQUIRE_MATCH, 69 /** 70 * Allow refspecs with an asterisk on only one side. This can create a 71 * many-to-one mapping between source and destination refs, so 72 * expandFromSource and expandFromDestination are not usable in this 73 * mode. 74 */ 75 ALLOW_MISMATCH 76 } 77 78 /** Whether a wildcard is allowed on one side but not the other. */ 79 private WildcardMode allowMismatchedWildcards; 80 81 /** Name of the ref(s) we would copy from. */ 82 private String srcName; 83 84 /** Name of the ref(s) we would copy into. */ 85 private String dstName; 86 87 /** 88 * Construct an empty RefSpec. 89 * <p> 90 * A newly created empty RefSpec is not suitable for use in most 91 * applications, as at least one field must be set to match a source name. 92 */ 93 public RefSpec() { 94 matching = false; 95 force = false; 96 wildcard = false; 97 srcName = Constants.HEAD; 98 dstName = null; 99 allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH; 100 } 101 102 /** 103 * Parse a ref specification for use during transport operations. 104 * <p> 105 * Specifications are typically one of the following forms: 106 * <ul> 107 * <li><code>refs/heads/master</code></li> 108 * <li><code>refs/heads/master:refs/remotes/origin/master</code></li> 109 * <li><code>refs/heads/*:refs/remotes/origin/*</code></li> 110 * <li><code>+refs/heads/master</code></li> 111 * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> 112 * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> 113 * <li><code>+refs/pull/*/head:refs/remotes/origin/pr/*</code></li> 114 * <li><code>:refs/heads/master</code></li> 115 * </ul> 116 * 117 * If the wildcard mode allows mismatches, then these ref specs are also 118 * valid: 119 * <ul> 120 * <li><code>refs/heads/*</code></li> 121 * <li><code>refs/heads/*:refs/heads/master</code></li> 122 * </ul> 123 * 124 * @param spec 125 * string describing the specification. 126 * @param mode 127 * whether to allow a wildcard on one side without a wildcard on 128 * the other. 129 * @throws java.lang.IllegalArgumentException 130 * the specification is invalid. 131 * @since 4.5 132 */ 133 public RefSpec(String spec, WildcardMode mode) { 134 this.allowMismatchedWildcards = mode; 135 String s = spec; 136 if (s.startsWith("+")) { //$NON-NLS-1$ 137 force = true; 138 s = s.substring(1); 139 } 140 141 boolean matchPushSpec = false; 142 final int c = s.lastIndexOf(':'); 143 if (c == 0) { 144 s = s.substring(1); 145 if (s.isEmpty()) { 146 matchPushSpec = true; 147 wildcard = true; 148 srcName = Constants.R_HEADS + '*'; 149 dstName = srcName; 150 } else { 151 if (isWildcard(s)) { 152 wildcard = true; 153 if (mode == WildcardMode.REQUIRE_MATCH) { 154 throw new IllegalArgumentException(MessageFormat 155 .format(JGitText.get().invalidWildcards, spec)); 156 } 157 } 158 dstName = checkValid(s); 159 } 160 } else if (c > 0) { 161 String src = s.substring(0, c); 162 String dst = s.substring(c + 1); 163 if (isWildcard(src) && isWildcard(dst)) { 164 // Both contain wildcard 165 wildcard = true; 166 } else if (isWildcard(src) || isWildcard(dst)) { 167 wildcard = true; 168 if (mode == WildcardMode.REQUIRE_MATCH) 169 throw new IllegalArgumentException(MessageFormat 170 .format(JGitText.get().invalidWildcards, spec)); 171 } 172 srcName = checkValid(src); 173 dstName = checkValid(dst); 174 } else { 175 if (isWildcard(s)) { 176 if (mode == WildcardMode.REQUIRE_MATCH) { 177 throw new IllegalArgumentException(MessageFormat 178 .format(JGitText.get().invalidWildcards, spec)); 179 } 180 wildcard = true; 181 } 182 srcName = checkValid(s); 183 } 184 matching = matchPushSpec; 185 } 186 187 /** 188 * Parse a ref specification for use during transport operations. 189 * <p> 190 * Specifications are typically one of the following forms: 191 * <ul> 192 * <li><code>refs/heads/master</code></li> 193 * <li><code>refs/heads/master:refs/remotes/origin/master</code></li> 194 * <li><code>refs/heads/*:refs/remotes/origin/*</code></li> 195 * <li><code>+refs/heads/master</code></li> 196 * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> 197 * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> 198 * <li><code>+refs/pull/*/head:refs/remotes/origin/pr/*</code></li> 199 * <li><code>:refs/heads/master</code></li> 200 * </ul> 201 * 202 * @param spec 203 * string describing the specification. 204 * @throws java.lang.IllegalArgumentException 205 * the specification is invalid. 206 */ 207 public RefSpec(String spec) { 208 this(spec, WildcardMode.REQUIRE_MATCH); 209 } 210 211 private RefSpec(RefSpec p) { 212 matching = false; 213 force = p.isForceUpdate(); 214 wildcard = p.isWildcard(); 215 srcName = p.getSource(); 216 dstName = p.getDestination(); 217 allowMismatchedWildcards = p.allowMismatchedWildcards; 218 } 219 220 /** 221 * Tells whether this {@link RefSpec} is the special "matching" RefSpec ":" 222 * for pushing. 223 * 224 * @return whether this is a "matching" RefSpec 225 * @since 6.1 226 */ 227 public boolean isMatching() { 228 return matching; 229 } 230 231 /** 232 * Check if this specification wants to forcefully update the destination. 233 * 234 * @return true if this specification asks for updates without merge tests. 235 */ 236 public boolean isForceUpdate() { 237 return force; 238 } 239 240 /** 241 * Create a new RefSpec with a different force update setting. 242 * 243 * @param forceUpdate 244 * new value for force update in the returned instance. 245 * @return a new RefSpec with force update as specified. 246 */ 247 public RefSpec setForceUpdate(boolean forceUpdate) { 248 final RefSpec r = new RefSpec(this); 249 r.matching = matching; 250 r.force = forceUpdate; 251 return r; 252 } 253 254 /** 255 * Check if this specification is actually a wildcard pattern. 256 * <p> 257 * If this is a wildcard pattern then the source and destination names 258 * returned by {@link #getSource()} and {@link #getDestination()} will not 259 * be actual ref names, but instead will be patterns. 260 * 261 * @return true if this specification could match more than one ref. 262 */ 263 public boolean isWildcard() { 264 return wildcard; 265 } 266 267 /** 268 * Get the source ref description. 269 * <p> 270 * During a fetch this is the name of the ref on the remote repository we 271 * are fetching from. During a push this is the name of the ref on the local 272 * repository we are pushing out from. 273 * 274 * @return name (or wildcard pattern) to match the source ref. 275 */ 276 public String getSource() { 277 return srcName; 278 } 279 280 /** 281 * Create a new RefSpec with a different source name setting. 282 * 283 * @param source 284 * new value for source in the returned instance. 285 * @return a new RefSpec with source as specified. 286 * @throws java.lang.IllegalStateException 287 * There is already a destination configured, and the wildcard 288 * status of the existing destination disagrees with the 289 * wildcard status of the new source. 290 */ 291 public RefSpec setSource(String source) { 292 final RefSpec r = new RefSpec(this); 293 r.srcName = checkValid(source); 294 if (isWildcard(r.srcName) && r.dstName == null) 295 throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); 296 if (isWildcard(r.srcName) != isWildcard(r.dstName)) 297 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 298 return r; 299 } 300 301 /** 302 * Get the destination ref description. 303 * <p> 304 * During a fetch this is the local tracking branch that will be updated 305 * with the new ObjectId after fetching is complete. During a push this is 306 * the remote ref that will be updated by the remote's receive-pack process. 307 * <p> 308 * If null during a fetch no tracking branch should be updated and the 309 * ObjectId should be stored transiently in order to prepare a merge. 310 * <p> 311 * If null during a push, use {@link #getSource()} instead. 312 * 313 * @return name (or wildcard) pattern to match the destination ref. 314 */ 315 public String getDestination() { 316 return dstName; 317 } 318 319 /** 320 * Create a new RefSpec with a different destination name setting. 321 * 322 * @param destination 323 * new value for destination in the returned instance. 324 * @return a new RefSpec with destination as specified. 325 * @throws java.lang.IllegalStateException 326 * There is already a source configured, and the wildcard status 327 * of the existing source disagrees with the wildcard status of 328 * the new destination. 329 */ 330 public RefSpec setDestination(String destination) { 331 final RefSpec r = new RefSpec(this); 332 r.dstName = checkValid(destination); 333 if (isWildcard(r.dstName) && r.srcName == null) 334 throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); 335 if (isWildcard(r.srcName) != isWildcard(r.dstName)) 336 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 337 return r; 338 } 339 340 /** 341 * Create a new RefSpec with a different source/destination name setting. 342 * 343 * @param source 344 * new value for source in the returned instance. 345 * @param destination 346 * new value for destination in the returned instance. 347 * @return a new RefSpec with destination as specified. 348 * @throws java.lang.IllegalArgumentException 349 * The wildcard status of the new source disagrees with the 350 * wildcard status of the new destination. 351 */ 352 public RefSpec setSourceDestination(String source, String destination) { 353 if (isWildcard(source) != isWildcard(destination)) 354 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 355 final RefSpec r = new RefSpec(this); 356 r.wildcard = isWildcard(source); 357 r.srcName = source; 358 r.dstName = destination; 359 return r; 360 } 361 362 /** 363 * Does this specification's source description match the ref name? 364 * 365 * @param r 366 * ref name that should be tested. 367 * @return true if the names match; false otherwise. 368 */ 369 public boolean matchSource(String r) { 370 return match(r, getSource()); 371 } 372 373 /** 374 * Does this specification's source description match the ref? 375 * 376 * @param r 377 * ref whose name should be tested. 378 * @return true if the names match; false otherwise. 379 */ 380 public boolean matchSource(Ref r) { 381 return match(r.getName(), getSource()); 382 } 383 384 /** 385 * Does this specification's destination description match the ref name? 386 * 387 * @param r 388 * ref name that should be tested. 389 * @return true if the names match; false otherwise. 390 */ 391 public boolean matchDestination(String r) { 392 return match(r, getDestination()); 393 } 394 395 /** 396 * Does this specification's destination description match the ref? 397 * 398 * @param r 399 * ref whose name should be tested. 400 * @return true if the names match; false otherwise. 401 */ 402 public boolean matchDestination(Ref r) { 403 return match(r.getName(), getDestination()); 404 } 405 406 /** 407 * Expand this specification to exactly match a ref name. 408 * <p> 409 * Callers must first verify the passed ref name matches this specification, 410 * otherwise expansion results may be unpredictable. 411 * 412 * @param r 413 * a ref name that matched our source specification. Could be a 414 * wildcard also. 415 * @return a new specification expanded from provided ref name. Result 416 * specification is wildcard if and only if provided ref name is 417 * wildcard. 418 * @throws java.lang.IllegalStateException 419 * when the RefSpec was constructed with wildcard mode that 420 * doesn't require matching wildcards. 421 */ 422 public RefSpec expandFromSource(String r) { 423 if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { 424 throw new IllegalStateException( 425 JGitText.get().invalidExpandWildcard); 426 } 427 return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this; 428 } 429 430 private RefSpec expandFromSourceImp(String name) { 431 final String psrc = srcName, pdst = dstName; 432 wildcard = false; 433 srcName = name; 434 dstName = expandWildcard(name, psrc, pdst); 435 return this; 436 } 437 438 /** 439 * Expand this specification to exactly match a ref. 440 * <p> 441 * Callers must first verify the passed ref matches this specification, 442 * otherwise expansion results may be unpredictable. 443 * 444 * @param r 445 * a ref that matched our source specification. Could be a 446 * wildcard also. 447 * @return a new specification expanded from provided ref name. Result 448 * specification is wildcard if and only if provided ref name is 449 * wildcard. 450 * @throws java.lang.IllegalStateException 451 * when the RefSpec was constructed with wildcard mode that 452 * doesn't require matching wildcards. 453 */ 454 public RefSpec expandFromSource(Ref r) { 455 return expandFromSource(r.getName()); 456 } 457 458 /** 459 * Expand this specification to exactly match a ref name. 460 * <p> 461 * Callers must first verify the passed ref name matches this specification, 462 * otherwise expansion results may be unpredictable. 463 * 464 * @param r 465 * a ref name that matched our destination specification. Could 466 * be a wildcard also. 467 * @return a new specification expanded from provided ref name. Result 468 * specification is wildcard if and only if provided ref name is 469 * wildcard. 470 * @throws java.lang.IllegalStateException 471 * when the RefSpec was constructed with wildcard mode that 472 * doesn't require matching wildcards. 473 */ 474 public RefSpec expandFromDestination(String r) { 475 if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { 476 throw new IllegalStateException( 477 JGitText.get().invalidExpandWildcard); 478 } 479 return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this; 480 } 481 482 private RefSpec expandFromDstImp(String name) { 483 final String psrc = srcName, pdst = dstName; 484 wildcard = false; 485 srcName = expandWildcard(name, pdst, psrc); 486 dstName = name; 487 return this; 488 } 489 490 /** 491 * Expand this specification to exactly match a ref. 492 * <p> 493 * Callers must first verify the passed ref matches this specification, 494 * otherwise expansion results may be unpredictable. 495 * 496 * @param r 497 * a ref that matched our destination specification. 498 * @return a new specification expanded from provided ref name. Result 499 * specification is wildcard if and only if provided ref name is 500 * wildcard. 501 * @throws java.lang.IllegalStateException 502 * when the RefSpec was constructed with wildcard mode that 503 * doesn't require matching wildcards. 504 */ 505 public RefSpec expandFromDestination(Ref r) { 506 return expandFromDestination(r.getName()); 507 } 508 509 private boolean match(String name, String s) { 510 if (s == null) 511 return false; 512 if (isWildcard(s)) { 513 int wildcardIndex = s.indexOf('*'); 514 String prefix = s.substring(0, wildcardIndex); 515 String suffix = s.substring(wildcardIndex + 1); 516 return name.length() > prefix.length() + suffix.length() 517 && name.startsWith(prefix) && name.endsWith(suffix); 518 } 519 return name.equals(s); 520 } 521 522 private static String expandWildcard(String name, String patternA, 523 String patternB) { 524 int a = patternA.indexOf('*'); 525 int trailingA = patternA.length() - (a + 1); 526 int b = patternB.indexOf('*'); 527 String match = name.substring(a, name.length() - trailingA); 528 return patternB.substring(0, b) + match + patternB.substring(b + 1); 529 } 530 531 private static String checkValid(String spec) { 532 if (spec != null && !isValid(spec)) 533 throw new IllegalArgumentException(MessageFormat.format( 534 JGitText.get().invalidRefSpec, spec)); 535 return spec; 536 } 537 538 private static boolean isValid(String s) { 539 if (s.startsWith("/")) //$NON-NLS-1$ 540 return false; 541 if (s.contains("//")) //$NON-NLS-1$ 542 return false; 543 if (s.endsWith("/")) //$NON-NLS-1$ 544 return false; 545 int i = s.indexOf('*'); 546 if (i != -1) { 547 if (s.indexOf('*', i + 1) > i) 548 return false; 549 } 550 return true; 551 } 552 553 /** {@inheritDoc} */ 554 @Override 555 public int hashCode() { 556 int hc = 0; 557 if (getSource() != null) 558 hc = hc * 31 + getSource().hashCode(); 559 if (getDestination() != null) 560 hc = hc * 31 + getDestination().hashCode(); 561 return hc; 562 } 563 564 /** {@inheritDoc} */ 565 @Override 566 public boolean equals(Object obj) { 567 if (!(obj instanceof RefSpec)) 568 return false; 569 final RefSpec b = (RefSpec) obj; 570 if (isForceUpdate() != b.isForceUpdate()) { 571 return false; 572 } 573 if (isMatching()) { 574 return b.isMatching(); 575 } else if (b.isMatching()) { 576 return false; 577 } 578 return isWildcard() == b.isWildcard() 579 && Objects.equals(getSource(), b.getSource()) 580 && Objects.equals(getDestination(), b.getDestination()); 581 } 582 583 /** {@inheritDoc} */ 584 @Override 585 public String toString() { 586 final StringBuilder r = new StringBuilder(); 587 if (isForceUpdate()) { 588 r.append('+'); 589 } 590 if (isMatching()) { 591 r.append(':'); 592 } else { 593 if (getSource() != null) { 594 r.append(getSource()); 595 } 596 if (getDestination() != null) { 597 r.append(':'); 598 r.append(getDestination()); 599 } 600 } 601 return r.toString(); 602 } 603 }