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