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(final 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 /** Name of the ref(s) we would copy from. */
86 private String srcName;
87
88 /** Name of the ref(s) we would copy into. */
89 private String dstName;
90
91 /**
92 * Construct an empty RefSpec.
93 * <p>
94 * A newly created empty RefSpec is not suitable for use in most
95 * applications, as at least one field must be set to match a source name.
96 */
97 public RefSpec() {
98 force = false;
99 wildcard = false;
100 srcName = Constants.HEAD;
101 dstName = null;
102 }
103
104 /**
105 * Parse a ref specification for use during transport operations.
106 * <p>
107 * Specifications are typically one of the following forms:
108 * <ul>
109 * <li><code>refs/heads/master</code></li>
110 * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
111 * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
112 * <li><code>+refs/heads/master</code></li>
113 * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
114 * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
115 * <li><code>+refs/pull/*/head:refs/remotes/origin/pr/*</code></li>
116 * <li><code>:refs/heads/master</code></li>
117 * </ul>
118 *
119 * @param spec
120 * string describing the specification.
121 * @throws IllegalArgumentException
122 * the specification is invalid.
123 */
124 public RefSpec(final String spec) {
125 String s = spec;
126 if (s.startsWith("+")) { //$NON-NLS-1$
127 force = true;
128 s = s.substring(1);
129 }
130
131 final int c = s.lastIndexOf(':');
132 if (c == 0) {
133 s = s.substring(1);
134 if (isWildcard(s))
135 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
136 dstName = checkValid(s);
137 } else if (c > 0) {
138 String src = s.substring(0, c);
139 String dst = s.substring(c + 1);
140 if (isWildcard(src) && isWildcard(dst)) {
141 // Both contain wildcard
142 wildcard = true;
143 } else if (isWildcard(src) || isWildcard(dst)) {
144 // If either source or destination has wildcard, the other one
145 // must have as well.
146 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
147 }
148 srcName = checkValid(src);
149 dstName = checkValid(dst);
150 } else {
151 if (isWildcard(s))
152 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
153 srcName = checkValid(s);
154 }
155 }
156
157 private RefSpec(final RefSpec p) {
158 force = p.isForceUpdate();
159 wildcard = p.isWildcard();
160 srcName = p.getSource();
161 dstName = p.getDestination();
162 }
163
164 /**
165 * Check if this specification wants to forcefully update the destination.
166 *
167 * @return true if this specification asks for updates without merge tests.
168 */
169 public boolean isForceUpdate() {
170 return force;
171 }
172
173 /**
174 * Create a new RefSpec with a different force update setting.
175 *
176 * @param forceUpdate
177 * new value for force update in the returned instance.
178 * @return a new RefSpec with force update as specified.
179 */
180 public RefSpec setForceUpdate(final boolean forceUpdate) {
181 final RefSpec r = new RefSpec(this);
182 r.force = forceUpdate;
183 return r;
184 }
185
186 /**
187 * Check if this specification is actually a wildcard pattern.
188 * <p>
189 * If this is a wildcard pattern then the source and destination names
190 * returned by {@link #getSource()} and {@link #getDestination()} will not
191 * be actual ref names, but instead will be patterns.
192 *
193 * @return true if this specification could match more than one ref.
194 */
195 public boolean isWildcard() {
196 return wildcard;
197 }
198
199 /**
200 * Get the source ref description.
201 * <p>
202 * During a fetch this is the name of the ref on the remote repository we
203 * are fetching from. During a push this is the name of the ref on the local
204 * repository we are pushing out from.
205 *
206 * @return name (or wildcard pattern) to match the source ref.
207 */
208 public String getSource() {
209 return srcName;
210 }
211
212 /**
213 * Create a new RefSpec with a different source name setting.
214 *
215 * @param source
216 * new value for source in the returned instance.
217 * @return a new RefSpec with source as specified.
218 * @throws IllegalStateException
219 * There is already a destination configured, and the wildcard
220 * status of the existing destination disagrees with the
221 * wildcard status of the new source.
222 */
223 public RefSpec setSource(final String source) {
224 final RefSpec r = new RefSpec(this);
225 r.srcName = checkValid(source);
226 if (isWildcard(r.srcName) && r.dstName == null)
227 throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard);
228 if (isWildcard(r.srcName) != isWildcard(r.dstName))
229 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
230 return r;
231 }
232
233 /**
234 * Get the destination ref description.
235 * <p>
236 * During a fetch this is the local tracking branch that will be updated
237 * with the new ObjectId after fetching is complete. During a push this is
238 * the remote ref that will be updated by the remote's receive-pack process.
239 * <p>
240 * If null during a fetch no tracking branch should be updated and the
241 * ObjectId should be stored transiently in order to prepare a merge.
242 * <p>
243 * If null during a push, use {@link #getSource()} instead.
244 *
245 * @return name (or wildcard) pattern to match the destination ref.
246 */
247 public String getDestination() {
248 return dstName;
249 }
250
251 /**
252 * Create a new RefSpec with a different destination name setting.
253 *
254 * @param destination
255 * new value for destination in the returned instance.
256 * @return a new RefSpec with destination as specified.
257 * @throws IllegalStateException
258 * There is already a source configured, and the wildcard status
259 * of the existing source disagrees with the wildcard status of
260 * the new destination.
261 */
262 public RefSpec setDestination(final String destination) {
263 final RefSpec r = new RefSpec(this);
264 r.dstName = checkValid(destination);
265 if (isWildcard(r.dstName) && r.srcName == null)
266 throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard);
267 if (isWildcard(r.srcName) != isWildcard(r.dstName))
268 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
269 return r;
270 }
271
272 /**
273 * Create a new RefSpec with a different source/destination name setting.
274 *
275 * @param source
276 * new value for source in the returned instance.
277 * @param destination
278 * new value for destination in the returned instance.
279 * @return a new RefSpec with destination as specified.
280 * @throws IllegalArgumentException
281 * The wildcard status of the new source disagrees with the
282 * wildcard status of the new destination.
283 */
284 public RefSpec setSourceDestination(final String source,
285 final String destination) {
286 if (isWildcard(source) != isWildcard(destination))
287 throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
288 final RefSpec r = new RefSpec(this);
289 r.wildcard = isWildcard(source);
290 r.srcName = source;
291 r.dstName = destination;
292 return r;
293 }
294
295 /**
296 * Does this specification's source description match the ref name?
297 *
298 * @param r
299 * ref name that should be tested.
300 * @return true if the names match; false otherwise.
301 */
302 public boolean matchSource(final String r) {
303 return match(r, getSource());
304 }
305
306 /**
307 * Does this specification's source description match the ref?
308 *
309 * @param r
310 * ref whose name should be tested.
311 * @return true if the names match; false otherwise.
312 */
313 public boolean matchSource(final Ref r) {
314 return match(r.getName(), getSource());
315 }
316
317 /**
318 * Does this specification's destination description match the ref name?
319 *
320 * @param r
321 * ref name that should be tested.
322 * @return true if the names match; false otherwise.
323 */
324 public boolean matchDestination(final String r) {
325 return match(r, getDestination());
326 }
327
328 /**
329 * Does this specification's destination description match the ref?
330 *
331 * @param r
332 * ref whose name should be tested.
333 * @return true if the names match; false otherwise.
334 */
335 public boolean matchDestination(final Ref r) {
336 return match(r.getName(), getDestination());
337 }
338
339 /**
340 * Expand this specification to exactly match a ref name.
341 * <p>
342 * Callers must first verify the passed ref name matches this specification,
343 * otherwise expansion results may be unpredictable.
344 *
345 * @param r
346 * a ref name that matched our source specification. Could be a
347 * wildcard also.
348 * @return a new specification expanded from provided ref name. Result
349 * specification is wildcard if and only if provided ref name is
350 * wildcard.
351 */
352 public RefSpec expandFromSource(final String r) {
353 return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
354 }
355
356 private RefSpec expandFromSourceImp(final String name) {
357 final String psrc = srcName, pdst = dstName;
358 wildcard = false;
359 srcName = name;
360 dstName = expandWildcard(name, psrc, pdst);
361 return this;
362 }
363
364 /**
365 * Expand this specification to exactly match a ref.
366 * <p>
367 * Callers must first verify the passed ref matches this specification,
368 * otherwise expansion results may be unpredictable.
369 *
370 * @param r
371 * a ref that matched our source specification. Could be a
372 * wildcard also.
373 * @return a new specification expanded from provided ref name. Result
374 * specification is wildcard if and only if provided ref name is
375 * wildcard.
376 */
377 public RefSpec expandFromSource(final Ref r) {
378 return expandFromSource(r.getName());
379 }
380
381 /**
382 * Expand this specification to exactly match a ref name.
383 * <p>
384 * Callers must first verify the passed ref name matches this specification,
385 * otherwise expansion results may be unpredictable.
386 *
387 * @param r
388 * a ref name that matched our destination specification. Could
389 * be a wildcard also.
390 * @return a new specification expanded from provided ref name. Result
391 * specification is wildcard if and only if provided ref name is
392 * wildcard.
393 */
394 public RefSpec expandFromDestination(final String r) {
395 return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
396 }
397
398 private RefSpec expandFromDstImp(final String name) {
399 final String psrc = srcName, pdst = dstName;
400 wildcard = false;
401 srcName = expandWildcard(name, pdst, psrc);
402 dstName = name;
403 return this;
404 }
405
406 /**
407 * Expand this specification to exactly match a ref.
408 * <p>
409 * Callers must first verify the passed ref matches this specification,
410 * otherwise expansion results may be unpredictable.
411 *
412 * @param r
413 * a ref that matched our destination specification.
414 * @return a new specification expanded from provided ref name. Result
415 * specification is wildcard if and only if provided ref name is
416 * wildcard.
417 */
418 public RefSpec expandFromDestination(final Ref r) {
419 return expandFromDestination(r.getName());
420 }
421
422 private boolean match(final String name, final String s) {
423 if (s == null)
424 return false;
425 if (isWildcard()) {
426 int wildcardIndex = s.indexOf('*');
427 String prefix = s.substring(0, wildcardIndex);
428 String suffix = s.substring(wildcardIndex + 1);
429 return name.length() > prefix.length() + suffix.length()
430 && name.startsWith(prefix) && name.endsWith(suffix);
431 }
432 return name.equals(s);
433 }
434
435 private static String expandWildcard(String name, String patternA,
436 String patternB) {
437 int a = patternA.indexOf('*');
438 int trailingA = patternA.length() - (a + 1);
439 int b = patternB.indexOf('*');
440 String match = name.substring(a, name.length() - trailingA);
441 return patternB.substring(0, b) + match + patternB.substring(b + 1);
442 }
443
444 private static String checkValid(String spec) {
445 if (spec != null && !isValid(spec))
446 throw new IllegalArgumentException(MessageFormat.format(
447 JGitText.get().invalidRefSpec, spec));
448 return spec;
449 }
450
451 private static boolean isValid(final String s) {
452 if (s.startsWith("/")) //$NON-NLS-1$
453 return false;
454 if (s.contains("//")) //$NON-NLS-1$
455 return false;
456 int i = s.indexOf('*');
457 if (i != -1) {
458 if (s.indexOf('*', i + 1) > i)
459 return false;
460 }
461 return true;
462 }
463
464 public int hashCode() {
465 int hc = 0;
466 if (getSource() != null)
467 hc = hc * 31 + getSource().hashCode();
468 if (getDestination() != null)
469 hc = hc * 31 + getDestination().hashCode();
470 return hc;
471 }
472
473 public boolean equals(final Object obj) {
474 if (!(obj instanceof RefSpec))
475 return false;
476 final RefSpec b = (RefSpec) obj;
477 if (isForceUpdate() != b.isForceUpdate())
478 return false;
479 if (isWildcard() != b.isWildcard())
480 return false;
481 if (!eq(getSource(), b.getSource()))
482 return false;
483 if (!eq(getDestination(), b.getDestination()))
484 return false;
485 return true;
486 }
487
488 private static boolean eq(final String a, final String b) {
489 if (a == b)
490 return true;
491 if (a == null || b == null)
492 return false;
493 return a.equals(b);
494 }
495
496 public String toString() {
497 final StringBuilder r = new StringBuilder();
498 if (isForceUpdate())
499 r.append('+');
500 if (getSource() != null)
501 r.append(getSource());
502 if (getDestination() != null) {
503 r.append(':');
504 r.append(getDestination());
505 }
506 return r.toString();
507 }
508 }