View Javadoc
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/&#42;/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/&#42;/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 RefSpecc" href="../../../../org/eclipse/jgit/transport/RefSpec.html#RefSpec">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 RefSpecrt/RefSpec.html#RefSpec">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 RefSpecrt/RefSpec.html#RefSpec">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 RefSpecrt/RefSpec.html#RefSpec">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 RefSpecrt/RefSpec.html#RefSpec">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 RefSpecef="../../../../org/eclipse/jgit/transport/RefSpec.html#RefSpec">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 }