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