View Javadoc
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/&#42;/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/&#42;/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 }