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(final 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 /** Name of the ref(s) we would copy from. */ 86 private String srcName; 87 88 /** Name of the ref(s) we would copy into. */ 89 private String dstName; 90 91 /** 92 * Construct an empty RefSpec. 93 * <p> 94 * A newly created empty RefSpec is not suitable for use in most 95 * applications, as at least one field must be set to match a source name. 96 */ 97 public RefSpec() { 98 force = false; 99 wildcard = false; 100 srcName = Constants.HEAD; 101 dstName = null; 102 } 103 104 /** 105 * Parse a ref specification for use during transport operations. 106 * <p> 107 * Specifications are typically one of the following forms: 108 * <ul> 109 * <li><code>refs/heads/master</code></li> 110 * <li><code>refs/heads/master:refs/remotes/origin/master</code></li> 111 * <li><code>refs/heads/*:refs/remotes/origin/*</code></li> 112 * <li><code>+refs/heads/master</code></li> 113 * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> 114 * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> 115 * <li><code>+refs/pull/*/head:refs/remotes/origin/pr/*</code></li> 116 * <li><code>:refs/heads/master</code></li> 117 * </ul> 118 * 119 * @param spec 120 * string describing the specification. 121 * @throws IllegalArgumentException 122 * the specification is invalid. 123 */ 124 public RefSpec(final String spec) { 125 String s = spec; 126 if (s.startsWith("+")) { //$NON-NLS-1$ 127 force = true; 128 s = s.substring(1); 129 } 130 131 final int c = s.lastIndexOf(':'); 132 if (c == 0) { 133 s = s.substring(1); 134 if (isWildcard(s)) 135 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); 136 dstName = checkValid(s); 137 } else if (c > 0) { 138 String src = s.substring(0, c); 139 String dst = s.substring(c + 1); 140 if (isWildcard(src) && isWildcard(dst)) { 141 // Both contain wildcard 142 wildcard = true; 143 } else if (isWildcard(src) || isWildcard(dst)) { 144 // If either source or destination has wildcard, the other one 145 // must have as well. 146 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); 147 } 148 srcName = checkValid(src); 149 dstName = checkValid(dst); 150 } else { 151 if (isWildcard(s)) 152 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); 153 srcName = checkValid(s); 154 } 155 } 156 157 private RefSpec(final RefSpec p) { 158 force = p.isForceUpdate(); 159 wildcard = p.isWildcard(); 160 srcName = p.getSource(); 161 dstName = p.getDestination(); 162 } 163 164 /** 165 * Check if this specification wants to forcefully update the destination. 166 * 167 * @return true if this specification asks for updates without merge tests. 168 */ 169 public boolean isForceUpdate() { 170 return force; 171 } 172 173 /** 174 * Create a new RefSpec with a different force update setting. 175 * 176 * @param forceUpdate 177 * new value for force update in the returned instance. 178 * @return a new RefSpec with force update as specified. 179 */ 180 public RefSpec setForceUpdate(final boolean forceUpdate) { 181 final RefSpec r = new RefSpec(this); 182 r.force = forceUpdate; 183 return r; 184 } 185 186 /** 187 * Check if this specification is actually a wildcard pattern. 188 * <p> 189 * If this is a wildcard pattern then the source and destination names 190 * returned by {@link #getSource()} and {@link #getDestination()} will not 191 * be actual ref names, but instead will be patterns. 192 * 193 * @return true if this specification could match more than one ref. 194 */ 195 public boolean isWildcard() { 196 return wildcard; 197 } 198 199 /** 200 * Get the source ref description. 201 * <p> 202 * During a fetch this is the name of the ref on the remote repository we 203 * are fetching from. During a push this is the name of the ref on the local 204 * repository we are pushing out from. 205 * 206 * @return name (or wildcard pattern) to match the source ref. 207 */ 208 public String getSource() { 209 return srcName; 210 } 211 212 /** 213 * Create a new RefSpec with a different source name setting. 214 * 215 * @param source 216 * new value for source in the returned instance. 217 * @return a new RefSpec with source as specified. 218 * @throws IllegalStateException 219 * There is already a destination configured, and the wildcard 220 * status of the existing destination disagrees with the 221 * wildcard status of the new source. 222 */ 223 public RefSpec setSource(final String source) { 224 final RefSpec r = new RefSpec(this); 225 r.srcName = checkValid(source); 226 if (isWildcard(r.srcName) && r.dstName == null) 227 throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); 228 if (isWildcard(r.srcName) != isWildcard(r.dstName)) 229 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 230 return r; 231 } 232 233 /** 234 * Get the destination ref description. 235 * <p> 236 * During a fetch this is the local tracking branch that will be updated 237 * with the new ObjectId after fetching is complete. During a push this is 238 * the remote ref that will be updated by the remote's receive-pack process. 239 * <p> 240 * If null during a fetch no tracking branch should be updated and the 241 * ObjectId should be stored transiently in order to prepare a merge. 242 * <p> 243 * If null during a push, use {@link #getSource()} instead. 244 * 245 * @return name (or wildcard) pattern to match the destination ref. 246 */ 247 public String getDestination() { 248 return dstName; 249 } 250 251 /** 252 * Create a new RefSpec with a different destination name setting. 253 * 254 * @param destination 255 * new value for destination in the returned instance. 256 * @return a new RefSpec with destination as specified. 257 * @throws IllegalStateException 258 * There is already a source configured, and the wildcard status 259 * of the existing source disagrees with the wildcard status of 260 * the new destination. 261 */ 262 public RefSpec setDestination(final String destination) { 263 final RefSpec r = new RefSpec(this); 264 r.dstName = checkValid(destination); 265 if (isWildcard(r.dstName) && r.srcName == null) 266 throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); 267 if (isWildcard(r.srcName) != isWildcard(r.dstName)) 268 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 269 return r; 270 } 271 272 /** 273 * Create a new RefSpec with a different source/destination name setting. 274 * 275 * @param source 276 * new value for source in the returned instance. 277 * @param destination 278 * new value for destination in the returned instance. 279 * @return a new RefSpec with destination as specified. 280 * @throws IllegalArgumentException 281 * The wildcard status of the new source disagrees with the 282 * wildcard status of the new destination. 283 */ 284 public RefSpec setSourceDestination(final String source, 285 final String destination) { 286 if (isWildcard(source) != isWildcard(destination)) 287 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); 288 final RefSpec r = new RefSpec(this); 289 r.wildcard = isWildcard(source); 290 r.srcName = source; 291 r.dstName = destination; 292 return r; 293 } 294 295 /** 296 * Does this specification's source description match the ref name? 297 * 298 * @param r 299 * ref name that should be tested. 300 * @return true if the names match; false otherwise. 301 */ 302 public boolean matchSource(final String r) { 303 return match(r, getSource()); 304 } 305 306 /** 307 * Does this specification's source description match the ref? 308 * 309 * @param r 310 * ref whose name should be tested. 311 * @return true if the names match; false otherwise. 312 */ 313 public boolean matchSource(final Ref r) { 314 return match(r.getName(), getSource()); 315 } 316 317 /** 318 * Does this specification's destination description match the ref name? 319 * 320 * @param r 321 * ref name that should be tested. 322 * @return true if the names match; false otherwise. 323 */ 324 public boolean matchDestination(final String r) { 325 return match(r, getDestination()); 326 } 327 328 /** 329 * Does this specification's destination description match the ref? 330 * 331 * @param r 332 * ref whose name should be tested. 333 * @return true if the names match; false otherwise. 334 */ 335 public boolean matchDestination(final Ref r) { 336 return match(r.getName(), getDestination()); 337 } 338 339 /** 340 * Expand this specification to exactly match a ref name. 341 * <p> 342 * Callers must first verify the passed ref name matches this specification, 343 * otherwise expansion results may be unpredictable. 344 * 345 * @param r 346 * a ref name that matched our source specification. Could be a 347 * wildcard also. 348 * @return a new specification expanded from provided ref name. Result 349 * specification is wildcard if and only if provided ref name is 350 * wildcard. 351 */ 352 public RefSpec expandFromSource(final String r) { 353 return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this; 354 } 355 356 private RefSpec expandFromSourceImp(final String name) { 357 final String psrc = srcName, pdst = dstName; 358 wildcard = false; 359 srcName = name; 360 dstName = expandWildcard(name, psrc, pdst); 361 return this; 362 } 363 364 /** 365 * Expand this specification to exactly match a ref. 366 * <p> 367 * Callers must first verify the passed ref matches this specification, 368 * otherwise expansion results may be unpredictable. 369 * 370 * @param r 371 * a ref that matched our source specification. Could be a 372 * wildcard also. 373 * @return a new specification expanded from provided ref name. Result 374 * specification is wildcard if and only if provided ref name is 375 * wildcard. 376 */ 377 public RefSpec expandFromSource(final Ref r) { 378 return expandFromSource(r.getName()); 379 } 380 381 /** 382 * Expand this specification to exactly match a ref name. 383 * <p> 384 * Callers must first verify the passed ref name matches this specification, 385 * otherwise expansion results may be unpredictable. 386 * 387 * @param r 388 * a ref name that matched our destination specification. Could 389 * be a wildcard also. 390 * @return a new specification expanded from provided ref name. Result 391 * specification is wildcard if and only if provided ref name is 392 * wildcard. 393 */ 394 public RefSpec expandFromDestination(final String r) { 395 return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this; 396 } 397 398 private RefSpec expandFromDstImp(final String name) { 399 final String psrc = srcName, pdst = dstName; 400 wildcard = false; 401 srcName = expandWildcard(name, pdst, psrc); 402 dstName = name; 403 return this; 404 } 405 406 /** 407 * Expand this specification to exactly match a ref. 408 * <p> 409 * Callers must first verify the passed ref matches this specification, 410 * otherwise expansion results may be unpredictable. 411 * 412 * @param r 413 * a ref that matched our destination specification. 414 * @return a new specification expanded from provided ref name. Result 415 * specification is wildcard if and only if provided ref name is 416 * wildcard. 417 */ 418 public RefSpec expandFromDestination(final Ref r) { 419 return expandFromDestination(r.getName()); 420 } 421 422 private boolean match(final String name, final String s) { 423 if (s == null) 424 return false; 425 if (isWildcard()) { 426 int wildcardIndex = s.indexOf('*'); 427 String prefix = s.substring(0, wildcardIndex); 428 String suffix = s.substring(wildcardIndex + 1); 429 return name.length() > prefix.length() + suffix.length() 430 && name.startsWith(prefix) && name.endsWith(suffix); 431 } 432 return name.equals(s); 433 } 434 435 private static String expandWildcard(String name, String patternA, 436 String patternB) { 437 int a = patternA.indexOf('*'); 438 int trailingA = patternA.length() - (a + 1); 439 int b = patternB.indexOf('*'); 440 String match = name.substring(a, name.length() - trailingA); 441 return patternB.substring(0, b) + match + patternB.substring(b + 1); 442 } 443 444 private static String checkValid(String spec) { 445 if (spec != null && !isValid(spec)) 446 throw new IllegalArgumentException(MessageFormat.format( 447 JGitText.get().invalidRefSpec, spec)); 448 return spec; 449 } 450 451 private static boolean isValid(final String s) { 452 if (s.startsWith("/")) //$NON-NLS-1$ 453 return false; 454 if (s.contains("//")) //$NON-NLS-1$ 455 return false; 456 int i = s.indexOf('*'); 457 if (i != -1) { 458 if (s.indexOf('*', i + 1) > i) 459 return false; 460 if (i > 0 && s.charAt(i - 1) != '/') 461 return false; 462 if (i < s.length() - 1 && s.charAt(i + 1) != '/') 463 return false; 464 } 465 return true; 466 } 467 468 public int hashCode() { 469 int hc = 0; 470 if (getSource() != null) 471 hc = hc * 31 + getSource().hashCode(); 472 if (getDestination() != null) 473 hc = hc * 31 + getDestination().hashCode(); 474 return hc; 475 } 476 477 public boolean equals(final Object obj) { 478 if (!(obj instanceof RefSpec)) 479 return false; 480 final RefSpec b = (RefSpec) obj; 481 if (isForceUpdate() != b.isForceUpdate()) 482 return false; 483 if (isWildcard() != b.isWildcard()) 484 return false; 485 if (!eq(getSource(), b.getSource())) 486 return false; 487 if (!eq(getDestination(), b.getDestination())) 488 return false; 489 return true; 490 } 491 492 private static boolean eq(final String a, final String b) { 493 if (a == b) 494 return true; 495 if (a == null || b == null) 496 return false; 497 return a.equals(b); 498 } 499 500 public String toString() { 501 final StringBuilder r = new StringBuilder(); 502 if (isForceUpdate()) 503 r.append('+'); 504 if (getSource() != null) 505 r.append(getSource()); 506 if (getDestination() != null) { 507 r.append(':'); 508 r.append(getDestination()); 509 } 510 return r.toString(); 511 } 512 }