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