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