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