org.eclipse.jgit.transport.RefSpec.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.transport.RefSpec.java

Source

/*
 * Copyright (C) 2008, 2013 Shawn O. Pearce <spearce@spearce.org>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

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.
 * <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.
     */
    public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$

    /**
     * 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)? */
    private boolean force;

    /** Is this specification actually a wildcard match? */
    private boolean wildcard;

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

    /** Whether a wildcard is allowed on one side but not the other. */
    private WildcardMode allowMismatchedWildcards;

    /** Name of the ref(s) we would copy from. */
    private String srcName;

    /** Name of the ref(s) we would copy into. */
    private String dstName;

    /**
     * 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.
     * <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.
     * <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.
     *
     * @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.
     *
     * @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.
     * <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.
     * <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.
     *
     * @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.
     * <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.
     *
     * @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.
     *
     * @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?
     *
     * @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?
     *
     * @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?
     *
     * @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?
     *
     * @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.
     * <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.
     * <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.
     * <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.
     * <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} */
    @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} */
    @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} */
    @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();
    }
}