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/*/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/*/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 }