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