/*
 * Copyright (C) 2008, 2013 Shawn O. Pearce <spearce@spearce.org> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.transport;

import java.io.Serializable;
import java.text.MessageFormat;

import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.util.References;

Describes how refs in one repository copy into another repository.

A ref specification provides matching support and limited rules to rewrite a reference in one repository to another reference in another repository.

/** * Describes how refs in one repository copy into another repository. * <p> * A ref specification provides matching support and limited rules to rewrite a * reference in one repository to another reference in another repository. */
public class RefSpec implements Serializable { private static final long serialVersionUID = 1L;
Suffix for wildcard ref spec component, that indicate matching all refs with specified prefix.
/** * Suffix for wildcard ref spec component, that indicate matching all refs * with specified prefix. */
public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$
Check whether provided string is a wildcard ref spec component.
Params:
  • s – ref spec component - string to test. Can be null.
Returns:true if provided string is a wildcard ref spec component.
/** * Check whether provided string is a wildcard ref spec component. * * @param s * ref spec component - string to test. Can be null. * @return true if provided string is a wildcard ref spec component. */
public static boolean isWildcard(String s) { return s != null && s.contains("*"); //$NON-NLS-1$ }
Does this specification ask for forced updated (rewind/reset)?
/** Does this specification ask for forced updated (rewind/reset)? */
private boolean force;
Is this specification actually a wildcard match?
/** Is this specification actually a wildcard match? */
private boolean wildcard;
How strict to be about wildcards.
Since:4.5
/** * How strict to be about wildcards. * * @since 4.5 */
public enum WildcardMode {
Reject refspecs with an asterisk on the source side and not the destination side or vice versa. This is the mode used by FetchCommand and PushCommand to create a one-to-one mapping between source and destination refs.
/** * Reject refspecs with an asterisk on the source side and not the * destination side or vice versa. This is the mode used by FetchCommand * and PushCommand to create a one-to-one mapping between source and * destination refs. */
REQUIRE_MATCH,
Allow refspecs with an asterisk on only one side. This can create a many-to-one mapping between source and destination refs, so expandFromSource and expandFromDestination are not usable in this mode.
/** * Allow refspecs with an asterisk on only one side. This can create a * many-to-one mapping between source and destination refs, so * expandFromSource and expandFromDestination are not usable in this * mode. */
ALLOW_MISMATCH }
Whether a wildcard is allowed on one side but not the other.
/** Whether a wildcard is allowed on one side but not the other. */
private WildcardMode allowMismatchedWildcards;
Name of the ref(s) we would copy from.
/** Name of the ref(s) we would copy from. */
private String srcName;
Name of the ref(s) we would copy into.
/** Name of the ref(s) we would copy into. */
private String dstName;
Construct an empty RefSpec.

A newly created empty RefSpec is not suitable for use in most applications, as at least one field must be set to match a source name.

/** * Construct an empty RefSpec. * <p> * A newly created empty RefSpec is not suitable for use in most * applications, as at least one field must be set to match a source name. */
public RefSpec() { force = false; wildcard = false; srcName = Constants.HEAD; dstName = null; allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH; }
Parse a ref specification for use during transport operations.

Specifications are typically one of the following forms:

  • refs/heads/master
  • refs/heads/master:refs/remotes/origin/master
  • refs/heads/*:refs/remotes/origin/*
  • +refs/heads/master
  • +refs/heads/master:refs/remotes/origin/master
  • +refs/heads/*:refs/remotes/origin/*
  • +refs/pull/*/head:refs/remotes/origin/pr/*
  • :refs/heads/master
If the wildcard mode allows mismatches, then these ref specs are also valid:
  • refs/heads/*
  • refs/heads/*:refs/heads/master
Params:
  • spec – string describing the specification.
  • mode – whether to allow a wildcard on one side without a wildcard on the other.
Throws:
Since:4.5
/** * Parse a ref specification for use during transport operations. * <p> * Specifications are typically one of the following forms: * <ul> * <li><code>refs/heads/master</code></li> * <li><code>refs/heads/master:refs/remotes/origin/master</code></li> * <li><code>refs/heads/*:refs/remotes/origin/*</code></li> * <li><code>+refs/heads/master</code></li> * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li> * <li><code>:refs/heads/master</code></li> * </ul> * * If the wildcard mode allows mismatches, then these ref specs are also * valid: * <ul> * <li><code>refs/heads/*</code></li> * <li><code>refs/heads/*:refs/heads/master</code></li> * </ul> * * @param spec * string describing the specification. * @param mode * whether to allow a wildcard on one side without a wildcard on * the other. * @throws java.lang.IllegalArgumentException * the specification is invalid. * @since 4.5 */
public RefSpec(String spec, WildcardMode mode) { this.allowMismatchedWildcards = mode; String s = spec; if (s.startsWith("+")) { //$NON-NLS-1$ force = true; s = s.substring(1); } final int c = s.lastIndexOf(':'); if (c == 0) { s = s.substring(1); if (isWildcard(s)) { wildcard = true; if (mode == WildcardMode.REQUIRE_MATCH) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } } dstName = checkValid(s); } else if (c > 0) { String src = s.substring(0, c); String dst = s.substring(c + 1); if (isWildcard(src) && isWildcard(dst)) { // Both contain wildcard wildcard = true; } else if (isWildcard(src) || isWildcard(dst)) { wildcard = true; if (mode == WildcardMode.REQUIRE_MATCH) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } srcName = checkValid(src); dstName = checkValid(dst); } else { if (isWildcard(s)) { if (mode == WildcardMode.REQUIRE_MATCH) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } wildcard = true; } srcName = checkValid(s); } }
Parse a ref specification for use during transport operations.

Specifications are typically one of the following forms:

  • refs/heads/master
  • refs/heads/master:refs/remotes/origin/master
  • refs/heads/*:refs/remotes/origin/*
  • +refs/heads/master
  • +refs/heads/master:refs/remotes/origin/master
  • +refs/heads/*:refs/remotes/origin/*
  • +refs/pull/*/head:refs/remotes/origin/pr/*
  • :refs/heads/master
Params:
  • spec – string describing the specification.
Throws:
/** * Parse a ref specification for use during transport operations. * <p> * Specifications are typically one of the following forms: * <ul> * <li><code>refs/heads/master</code></li> * <li><code>refs/heads/master:refs/remotes/origin/master</code></li> * <li><code>refs/heads/*:refs/remotes/origin/*</code></li> * <li><code>+refs/heads/master</code></li> * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li> * <li><code>:refs/heads/master</code></li> * </ul> * * @param spec * string describing the specification. * @throws java.lang.IllegalArgumentException * the specification is invalid. */
public RefSpec(String spec) { this(spec, WildcardMode.REQUIRE_MATCH); } private RefSpec(RefSpec p) { force = p.isForceUpdate(); wildcard = p.isWildcard(); srcName = p.getSource(); dstName = p.getDestination(); allowMismatchedWildcards = p.allowMismatchedWildcards; }
Check if this specification wants to forcefully update the destination.
Returns:true if this specification asks for updates without merge tests.
/** * Check if this specification wants to forcefully update the destination. * * @return true if this specification asks for updates without merge tests. */
public boolean isForceUpdate() { return force; }
Create a new RefSpec with a different force update setting.
Params:
  • forceUpdate – new value for force update in the returned instance.
Returns:a new RefSpec with force update as specified.
/** * Create a new RefSpec with a different force update setting. * * @param forceUpdate * new value for force update in the returned instance. * @return a new RefSpec with force update as specified. */
public RefSpec setForceUpdate(boolean forceUpdate) { final RefSpec r = new RefSpec(this); r.force = forceUpdate; return r; }
Check if this specification is actually a wildcard pattern.

If this is a wildcard pattern then the source and destination names returned by getSource() and getDestination() will not be actual ref names, but instead will be patterns.

Returns:true if this specification could match more than one ref.
/** * Check if this specification is actually a wildcard pattern. * <p> * If this is a wildcard pattern then the source and destination names * returned by {@link #getSource()} and {@link #getDestination()} will not * be actual ref names, but instead will be patterns. * * @return true if this specification could match more than one ref. */
public boolean isWildcard() { return wildcard; }
Get the source ref description.

During a fetch this is the name of the ref on the remote repository we are fetching from. During a push this is the name of the ref on the local repository we are pushing out from.

Returns:name (or wildcard pattern) to match the source ref.
/** * Get the source ref description. * <p> * During a fetch this is the name of the ref on the remote repository we * are fetching from. During a push this is the name of the ref on the local * repository we are pushing out from. * * @return name (or wildcard pattern) to match the source ref. */
public String getSource() { return srcName; }
Create a new RefSpec with a different source name setting.
Params:
  • source – new value for source in the returned instance.
Throws:
  • IllegalStateException – There is already a destination configured, and the wildcard status of the existing destination disagrees with the wildcard status of the new source.
Returns:a new RefSpec with source as specified.
/** * Create a new RefSpec with a different source name setting. * * @param source * new value for source in the returned instance. * @return a new RefSpec with source as specified. * @throws java.lang.IllegalStateException * There is already a destination configured, and the wildcard * status of the existing destination disagrees with the * wildcard status of the new source. */
public RefSpec setSource(String source) { final RefSpec r = new RefSpec(this); r.srcName = checkValid(source); if (isWildcard(r.srcName) && r.dstName == null) throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); if (isWildcard(r.srcName) != isWildcard(r.dstName)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); return r; }
Get the destination ref description.

During a fetch this is the local tracking branch that will be updated with the new ObjectId after fetching is complete. During a push this is the remote ref that will be updated by the remote's receive-pack process.

If null during a fetch no tracking branch should be updated and the ObjectId should be stored transiently in order to prepare a merge.

If null during a push, use getSource() instead.

Returns:name (or wildcard) pattern to match the destination ref.
/** * Get the destination ref description. * <p> * During a fetch this is the local tracking branch that will be updated * with the new ObjectId after fetching is complete. During a push this is * the remote ref that will be updated by the remote's receive-pack process. * <p> * If null during a fetch no tracking branch should be updated and the * ObjectId should be stored transiently in order to prepare a merge. * <p> * If null during a push, use {@link #getSource()} instead. * * @return name (or wildcard) pattern to match the destination ref. */
public String getDestination() { return dstName; }
Create a new RefSpec with a different destination name setting.
Params:
  • destination – new value for destination in the returned instance.
Throws:
  • IllegalStateException – There is already a source configured, and the wildcard status of the existing source disagrees with the wildcard status of the new destination.
Returns:a new RefSpec with destination as specified.
/** * Create a new RefSpec with a different destination name setting. * * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. * @throws java.lang.IllegalStateException * There is already a source configured, and the wildcard status * of the existing source disagrees with the wildcard status of * the new destination. */
public RefSpec setDestination(String destination) { final RefSpec r = new RefSpec(this); r.dstName = checkValid(destination); if (isWildcard(r.dstName) && r.srcName == null) throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); if (isWildcard(r.srcName) != isWildcard(r.dstName)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); return r; }
Create a new RefSpec with a different source/destination name setting.
Params:
  • source – new value for source in the returned instance.
  • destination – new value for destination in the returned instance.
Throws:
  • IllegalArgumentException – The wildcard status of the new source disagrees with the wildcard status of the new destination.
Returns:a new RefSpec with destination as specified.
/** * Create a new RefSpec with a different source/destination name setting. * * @param source * new value for source in the returned instance. * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. * @throws java.lang.IllegalArgumentException * The wildcard status of the new source disagrees with the * wildcard status of the new destination. */
public RefSpec setSourceDestination(final String source, final String destination) { if (isWildcard(source) != isWildcard(destination)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); final RefSpec r = new RefSpec(this); r.wildcard = isWildcard(source); r.srcName = source; r.dstName = destination; return r; }
Does this specification's source description match the ref name?
Params:
  • r – ref name that should be tested.
Returns:true if the names match; false otherwise.
/** * Does this specification's source description match the ref name? * * @param r * ref name that should be tested. * @return true if the names match; false otherwise. */
public boolean matchSource(String r) { return match(r, getSource()); }
Does this specification's source description match the ref?
Params:
  • r – ref whose name should be tested.
Returns:true if the names match; false otherwise.
/** * Does this specification's source description match the ref? * * @param r * ref whose name should be tested. * @return true if the names match; false otherwise. */
public boolean matchSource(Ref r) { return match(r.getName(), getSource()); }
Does this specification's destination description match the ref name?
Params:
  • r – ref name that should be tested.
Returns:true if the names match; false otherwise.
/** * Does this specification's destination description match the ref name? * * @param r * ref name that should be tested. * @return true if the names match; false otherwise. */
public boolean matchDestination(String r) { return match(r, getDestination()); }
Does this specification's destination description match the ref?
Params:
  • r – ref whose name should be tested.
Returns:true if the names match; false otherwise.
/** * Does this specification's destination description match the ref? * * @param r * ref whose name should be tested. * @return true if the names match; false otherwise. */
public boolean matchDestination(Ref r) { return match(r.getName(), getDestination()); }
Expand this specification to exactly match a ref name.

Callers must first verify the passed ref name matches this specification, otherwise expansion results may be unpredictable.

Params:
  • r – a ref name that matched our source specification. Could be a wildcard also.
Throws:
  • IllegalStateException – when the RefSpec was constructed with wildcard mode that doesn't require matching wildcards.
Returns:a new specification expanded from provided ref name. Result specification is wildcard if and only if provided ref name is wildcard.
/** * Expand this specification to exactly match a ref name. * <p> * Callers must first verify the passed ref name matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref name that matched our source specification. Could be a * wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */
public RefSpec expandFromSource(String r) { if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { throw new IllegalStateException( JGitText.get().invalidExpandWildcard); } return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this; } private RefSpec expandFromSourceImp(String name) { final String psrc = srcName, pdst = dstName; wildcard = false; srcName = name; dstName = expandWildcard(name, psrc, pdst); return this; }
Expand this specification to exactly match a ref.

Callers must first verify the passed ref matches this specification, otherwise expansion results may be unpredictable.

Params:
  • r – a ref that matched our source specification. Could be a wildcard also.
Throws:
  • IllegalStateException – when the RefSpec was constructed with wildcard mode that doesn't require matching wildcards.
Returns:a new specification expanded from provided ref name. Result specification is wildcard if and only if provided ref name is wildcard.
/** * Expand this specification to exactly match a ref. * <p> * Callers must first verify the passed ref matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref that matched our source specification. Could be a * wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */
public RefSpec expandFromSource(Ref r) { return expandFromSource(r.getName()); }
Expand this specification to exactly match a ref name.

Callers must first verify the passed ref name matches this specification, otherwise expansion results may be unpredictable.

Params:
  • r – a ref name that matched our destination specification. Could be a wildcard also.
Throws:
  • IllegalStateException – when the RefSpec was constructed with wildcard mode that doesn't require matching wildcards.
Returns:a new specification expanded from provided ref name. Result specification is wildcard if and only if provided ref name is wildcard.
/** * Expand this specification to exactly match a ref name. * <p> * Callers must first verify the passed ref name matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref name that matched our destination specification. Could * be a wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */
public RefSpec expandFromDestination(String r) { if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { throw new IllegalStateException( JGitText.get().invalidExpandWildcard); } return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this; } private RefSpec expandFromDstImp(String name) { final String psrc = srcName, pdst = dstName; wildcard = false; srcName = expandWildcard(name, pdst, psrc); dstName = name; return this; }
Expand this specification to exactly match a ref.

Callers must first verify the passed ref matches this specification, otherwise expansion results may be unpredictable.

Params:
  • r – a ref that matched our destination specification.
Throws:
  • IllegalStateException – when the RefSpec was constructed with wildcard mode that doesn't require matching wildcards.
Returns:a new specification expanded from provided ref name. Result specification is wildcard if and only if provided ref name is wildcard.
/** * Expand this specification to exactly match a ref. * <p> * Callers must first verify the passed ref matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref that matched our destination specification. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */
public RefSpec expandFromDestination(Ref r) { return expandFromDestination(r.getName()); } private boolean match(String name, String s) { if (s == null) return false; if (isWildcard(s)) { int wildcardIndex = s.indexOf('*'); String prefix = s.substring(0, wildcardIndex); String suffix = s.substring(wildcardIndex + 1); return name.length() > prefix.length() + suffix.length() && name.startsWith(prefix) && name.endsWith(suffix); } return name.equals(s); } private static String expandWildcard(String name, String patternA, String patternB) { int a = patternA.indexOf('*'); int trailingA = patternA.length() - (a + 1); int b = patternB.indexOf('*'); String match = name.substring(a, name.length() - trailingA); return patternB.substring(0, b) + match + patternB.substring(b + 1); } private static String checkValid(String spec) { if (spec != null && !isValid(spec)) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidRefSpec, spec)); return spec; } private static boolean isValid(String s) { if (s.startsWith("/")) //$NON-NLS-1$ return false; if (s.contains("//")) //$NON-NLS-1$ return false; if (s.endsWith("/")) //$NON-NLS-1$ return false; int i = s.indexOf('*'); if (i != -1) { if (s.indexOf('*', i + 1) > i) return false; } return true; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public int hashCode() { int hc = 0; if (getSource() != null) hc = hc * 31 + getSource().hashCode(); if (getDestination() != null) hc = hc * 31 + getDestination().hashCode(); return hc; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public boolean equals(Object obj) { if (!(obj instanceof RefSpec)) return false; final RefSpec b = (RefSpec) obj; if (isForceUpdate() != b.isForceUpdate()) return false; if (isWildcard() != b.isWildcard()) return false; if (!eq(getSource(), b.getSource())) return false; if (!eq(getDestination(), b.getDestination())) return false; return true; } private static boolean eq(String a, String b) { if (References.isSameObject(a, b)) { return true; } if (a == null || b == null) return false; return a.equals(b); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public String toString() { final StringBuilder r = new StringBuilder(); if (isForceUpdate()) r.append('+'); if (getSource() != null) r.append(getSource()); if (getDestination() != null) { r.append(':'); r.append(getDestination()); } return r.toString(); } }