RefSpec.java

  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. package org.eclipse.jgit.transport;

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

  13. import org.eclipse.jgit.internal.JGitText;
  14. import org.eclipse.jgit.lib.Constants;
  15. import org.eclipse.jgit.lib.Ref;
  16. import org.eclipse.jgit.util.References;

  17. /**
  18.  * Describes how refs in one repository copy into another repository.
  19.  * <p>
  20.  * A ref specification provides matching support and limited rules to rewrite a
  21.  * reference in one repository to another reference in another repository.
  22.  */
  23. public class RefSpec implements Serializable {
  24.     private static final long serialVersionUID = 1L;

  25.     /**
  26.      * Suffix for wildcard ref spec component, that indicate matching all refs
  27.      * with specified prefix.
  28.      */
  29.     public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$

  30.     /**
  31.      * Check whether provided string is a wildcard ref spec component.
  32.      *
  33.      * @param s
  34.      *            ref spec component - string to test. Can be null.
  35.      * @return true if provided string is a wildcard ref spec component.
  36.      */
  37.     public static boolean isWildcard(String s) {
  38.         return s != null && s.contains("*"); //$NON-NLS-1$
  39.     }

  40.     /** Does this specification ask for forced updated (rewind/reset)? */
  41.     private boolean force;

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

  44.     /**
  45.      * How strict to be about wildcards.
  46.      *
  47.      * @since 4.5
  48.      */
  49.     public enum WildcardMode {
  50.         /**
  51.          * Reject refspecs with an asterisk on the source side and not the
  52.          * destination side or vice versa. This is the mode used by FetchCommand
  53.          * and PushCommand to create a one-to-one mapping between source and
  54.          * destination refs.
  55.          */
  56.         REQUIRE_MATCH,
  57.         /**
  58.          * Allow refspecs with an asterisk on only one side. This can create a
  59.          * many-to-one mapping between source and destination refs, so
  60.          * expandFromSource and expandFromDestination are not usable in this
  61.          * mode.
  62.          */
  63.         ALLOW_MISMATCH
  64.     }
  65.     /** Whether a wildcard is allowed on one side but not the other. */
  66.     private WildcardMode allowMismatchedWildcards;

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

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

  71.     /**
  72.      * Construct an empty RefSpec.
  73.      * <p>
  74.      * A newly created empty RefSpec is not suitable for use in most
  75.      * applications, as at least one field must be set to match a source name.
  76.      */
  77.     public RefSpec() {
  78.         force = false;
  79.         wildcard = false;
  80.         srcName = Constants.HEAD;
  81.         dstName = null;
  82.         allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH;
  83.     }

  84.     /**
  85.      * Parse a ref specification for use during transport operations.
  86.      * <p>
  87.      * Specifications are typically one of the following forms:
  88.      * <ul>
  89.      * <li><code>refs/heads/master</code></li>
  90.      * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
  91.      * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
  92.      * <li><code>+refs/heads/master</code></li>
  93.      * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
  94.      * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
  95.      * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
  96.      * <li><code>:refs/heads/master</code></li>
  97.      * </ul>
  98.      *
  99.      * If the wildcard mode allows mismatches, then these ref specs are also
  100.      * valid:
  101.      * <ul>
  102.      * <li><code>refs/heads/*</code></li>
  103.      * <li><code>refs/heads/*:refs/heads/master</code></li>
  104.      * </ul>
  105.      *
  106.      * @param spec
  107.      *            string describing the specification.
  108.      * @param mode
  109.      *            whether to allow a wildcard on one side without a wildcard on
  110.      *            the other.
  111.      * @throws java.lang.IllegalArgumentException
  112.      *             the specification is invalid.
  113.      * @since 4.5
  114.      */
  115.     public RefSpec(String spec, WildcardMode mode) {
  116.         this.allowMismatchedWildcards = mode;
  117.         String s = spec;
  118.         if (s.startsWith("+")) { //$NON-NLS-1$
  119.             force = true;
  120.             s = s.substring(1);
  121.         }

  122.         final int c = s.lastIndexOf(':');
  123.         if (c == 0) {
  124.             s = s.substring(1);
  125.             if (isWildcard(s)) {
  126.                 wildcard = true;
  127.                 if (mode == WildcardMode.REQUIRE_MATCH) {
  128.                     throw new IllegalArgumentException(MessageFormat
  129.                             .format(JGitText.get().invalidWildcards, spec));
  130.                 }
  131.             }
  132.             dstName = checkValid(s);
  133.         } else if (c > 0) {
  134.             String src = s.substring(0, c);
  135.             String dst = s.substring(c + 1);
  136.             if (isWildcard(src) && isWildcard(dst)) {
  137.                 // Both contain wildcard
  138.                 wildcard = true;
  139.             } else if (isWildcard(src) || isWildcard(dst)) {
  140.                 wildcard = true;
  141.                 if (mode == WildcardMode.REQUIRE_MATCH)
  142.                     throw new IllegalArgumentException(MessageFormat
  143.                             .format(JGitText.get().invalidWildcards, spec));
  144.             }
  145.             srcName = checkValid(src);
  146.             dstName = checkValid(dst);
  147.         } else {
  148.             if (isWildcard(s)) {
  149.                 if (mode == WildcardMode.REQUIRE_MATCH) {
  150.                     throw new IllegalArgumentException(MessageFormat
  151.                             .format(JGitText.get().invalidWildcards, spec));
  152.                 }
  153.                 wildcard = true;
  154.             }
  155.             srcName = checkValid(s);
  156.         }
  157.     }

  158.     /**
  159.      * Parse a ref specification for use during transport operations.
  160.      * <p>
  161.      * Specifications are typically one of the following forms:
  162.      * <ul>
  163.      * <li><code>refs/heads/master</code></li>
  164.      * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
  165.      * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
  166.      * <li><code>+refs/heads/master</code></li>
  167.      * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
  168.      * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
  169.      * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
  170.      * <li><code>:refs/heads/master</code></li>
  171.      * </ul>
  172.      *
  173.      * @param spec
  174.      *            string describing the specification.
  175.      * @throws java.lang.IllegalArgumentException
  176.      *             the specification is invalid.
  177.      */
  178.     public RefSpec(String spec) {
  179.         this(spec, WildcardMode.REQUIRE_MATCH);
  180.     }

  181.     private RefSpec(RefSpec p) {
  182.         force = p.isForceUpdate();
  183.         wildcard = p.isWildcard();
  184.         srcName = p.getSource();
  185.         dstName = p.getDestination();
  186.         allowMismatchedWildcards = p.allowMismatchedWildcards;
  187.     }

  188.     /**
  189.      * Check if this specification wants to forcefully update the destination.
  190.      *
  191.      * @return true if this specification asks for updates without merge tests.
  192.      */
  193.     public boolean isForceUpdate() {
  194.         return force;
  195.     }

  196.     /**
  197.      * Create a new RefSpec with a different force update setting.
  198.      *
  199.      * @param forceUpdate
  200.      *            new value for force update in the returned instance.
  201.      * @return a new RefSpec with force update as specified.
  202.      */
  203.     public RefSpec setForceUpdate(boolean forceUpdate) {
  204.         final RefSpec r = new RefSpec(this);
  205.         r.force = forceUpdate;
  206.         return r;
  207.     }

  208.     /**
  209.      * Check if this specification is actually a wildcard pattern.
  210.      * <p>
  211.      * If this is a wildcard pattern then the source and destination names
  212.      * returned by {@link #getSource()} and {@link #getDestination()} will not
  213.      * be actual ref names, but instead will be patterns.
  214.      *
  215.      * @return true if this specification could match more than one ref.
  216.      */
  217.     public boolean isWildcard() {
  218.         return wildcard;
  219.     }

  220.     /**
  221.      * Get the source ref description.
  222.      * <p>
  223.      * During a fetch this is the name of the ref on the remote repository we
  224.      * are fetching from. During a push this is the name of the ref on the local
  225.      * repository we are pushing out from.
  226.      *
  227.      * @return name (or wildcard pattern) to match the source ref.
  228.      */
  229.     public String getSource() {
  230.         return srcName;
  231.     }

  232.     /**
  233.      * Create a new RefSpec with a different source name setting.
  234.      *
  235.      * @param source
  236.      *            new value for source in the returned instance.
  237.      * @return a new RefSpec with source as specified.
  238.      * @throws java.lang.IllegalStateException
  239.      *             There is already a destination configured, and the wildcard
  240.      *             status of the existing destination disagrees with the
  241.      *             wildcard status of the new source.
  242.      */
  243.     public RefSpec setSource(String source) {
  244.         final RefSpec r = new RefSpec(this);
  245.         r.srcName = checkValid(source);
  246.         if (isWildcard(r.srcName) && r.dstName == null)
  247.             throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard);
  248.         if (isWildcard(r.srcName) != isWildcard(r.dstName))
  249.             throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  250.         return r;
  251.     }

  252.     /**
  253.      * Get the destination ref description.
  254.      * <p>
  255.      * During a fetch this is the local tracking branch that will be updated
  256.      * with the new ObjectId after fetching is complete. During a push this is
  257.      * the remote ref that will be updated by the remote's receive-pack process.
  258.      * <p>
  259.      * If null during a fetch no tracking branch should be updated and the
  260.      * ObjectId should be stored transiently in order to prepare a merge.
  261.      * <p>
  262.      * If null during a push, use {@link #getSource()} instead.
  263.      *
  264.      * @return name (or wildcard) pattern to match the destination ref.
  265.      */
  266.     public String getDestination() {
  267.         return dstName;
  268.     }

  269.     /**
  270.      * Create a new RefSpec with a different destination name setting.
  271.      *
  272.      * @param destination
  273.      *            new value for destination in the returned instance.
  274.      * @return a new RefSpec with destination as specified.
  275.      * @throws java.lang.IllegalStateException
  276.      *             There is already a source configured, and the wildcard status
  277.      *             of the existing source disagrees with the wildcard status of
  278.      *             the new destination.
  279.      */
  280.     public RefSpec setDestination(String destination) {
  281.         final RefSpec r = new RefSpec(this);
  282.         r.dstName = checkValid(destination);
  283.         if (isWildcard(r.dstName) && r.srcName == null)
  284.             throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard);
  285.         if (isWildcard(r.srcName) != isWildcard(r.dstName))
  286.             throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  287.         return r;
  288.     }

  289.     /**
  290.      * Create a new RefSpec with a different source/destination name setting.
  291.      *
  292.      * @param source
  293.      *            new value for source in the returned instance.
  294.      * @param destination
  295.      *            new value for destination in the returned instance.
  296.      * @return a new RefSpec with destination as specified.
  297.      * @throws java.lang.IllegalArgumentException
  298.      *             The wildcard status of the new source disagrees with the
  299.      *             wildcard status of the new destination.
  300.      */
  301.     public RefSpec setSourceDestination(final String source,
  302.             final String destination) {
  303.         if (isWildcard(source) != isWildcard(destination))
  304.             throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  305.         final RefSpec r = new RefSpec(this);
  306.         r.wildcard = isWildcard(source);
  307.         r.srcName = source;
  308.         r.dstName = destination;
  309.         return r;
  310.     }

  311.     /**
  312.      * Does this specification's source description match the ref name?
  313.      *
  314.      * @param r
  315.      *            ref name that should be tested.
  316.      * @return true if the names match; false otherwise.
  317.      */
  318.     public boolean matchSource(String r) {
  319.         return match(r, getSource());
  320.     }

  321.     /**
  322.      * Does this specification's source description match the ref?
  323.      *
  324.      * @param r
  325.      *            ref whose name should be tested.
  326.      * @return true if the names match; false otherwise.
  327.      */
  328.     public boolean matchSource(Ref r) {
  329.         return match(r.getName(), getSource());
  330.     }

  331.     /**
  332.      * Does this specification's destination description match the ref name?
  333.      *
  334.      * @param r
  335.      *            ref name that should be tested.
  336.      * @return true if the names match; false otherwise.
  337.      */
  338.     public boolean matchDestination(String r) {
  339.         return match(r, getDestination());
  340.     }

  341.     /**
  342.      * Does this specification's destination description match the ref?
  343.      *
  344.      * @param r
  345.      *            ref whose name should be tested.
  346.      * @return true if the names match; false otherwise.
  347.      */
  348.     public boolean matchDestination(Ref r) {
  349.         return match(r.getName(), getDestination());
  350.     }

  351.     /**
  352.      * Expand this specification to exactly match a ref name.
  353.      * <p>
  354.      * Callers must first verify the passed ref name matches this specification,
  355.      * otherwise expansion results may be unpredictable.
  356.      *
  357.      * @param r
  358.      *            a ref name that matched our source specification. Could be a
  359.      *            wildcard also.
  360.      * @return a new specification expanded from provided ref name. Result
  361.      *         specification is wildcard if and only if provided ref name is
  362.      *         wildcard.
  363.      * @throws java.lang.IllegalStateException
  364.      *             when the RefSpec was constructed with wildcard mode that
  365.      *             doesn't require matching wildcards.
  366.      */
  367.     public RefSpec expandFromSource(String r) {
  368.         if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
  369.             throw new IllegalStateException(
  370.                     JGitText.get().invalidExpandWildcard);
  371.         }
  372.         return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
  373.     }

  374.     private RefSpec expandFromSourceImp(String name) {
  375.         final String psrc = srcName, pdst = dstName;
  376.         wildcard = false;
  377.         srcName = name;
  378.         dstName = expandWildcard(name, psrc, pdst);
  379.         return this;
  380.     }

  381.     /**
  382.      * Expand this specification to exactly match a ref.
  383.      * <p>
  384.      * Callers must first verify the passed ref matches this specification,
  385.      * otherwise expansion results may be unpredictable.
  386.      *
  387.      * @param r
  388.      *            a ref that matched our source specification. Could be a
  389.      *            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.      * @throws java.lang.IllegalStateException
  394.      *             when the RefSpec was constructed with wildcard mode that
  395.      *             doesn't require matching wildcards.
  396.      */
  397.     public RefSpec expandFromSource(Ref r) {
  398.         return expandFromSource(r.getName());
  399.     }

  400.     /**
  401.      * Expand this specification to exactly match a ref name.
  402.      * <p>
  403.      * Callers must first verify the passed ref name matches this specification,
  404.      * otherwise expansion results may be unpredictable.
  405.      *
  406.      * @param r
  407.      *            a ref name that matched our destination specification. Could
  408.      *            be a wildcard also.
  409.      * @return a new specification expanded from provided ref name. Result
  410.      *         specification is wildcard if and only if provided ref name is
  411.      *         wildcard.
  412.      * @throws java.lang.IllegalStateException
  413.      *             when the RefSpec was constructed with wildcard mode that
  414.      *             doesn't require matching wildcards.
  415.      */
  416.     public RefSpec expandFromDestination(String r) {
  417.         if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
  418.             throw new IllegalStateException(
  419.                     JGitText.get().invalidExpandWildcard);
  420.         }
  421.         return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
  422.     }

  423.     private RefSpec expandFromDstImp(String name) {
  424.         final String psrc = srcName, pdst = dstName;
  425.         wildcard = false;
  426.         srcName = expandWildcard(name, pdst, psrc);
  427.         dstName = name;
  428.         return this;
  429.     }

  430.     /**
  431.      * Expand this specification to exactly match a ref.
  432.      * <p>
  433.      * Callers must first verify the passed ref matches this specification,
  434.      * otherwise expansion results may be unpredictable.
  435.      *
  436.      * @param r
  437.      *            a ref that matched our destination specification.
  438.      * @return a new specification expanded from provided ref name. Result
  439.      *         specification is wildcard if and only if provided ref name is
  440.      *         wildcard.
  441.      * @throws java.lang.IllegalStateException
  442.      *             when the RefSpec was constructed with wildcard mode that
  443.      *             doesn't require matching wildcards.
  444.      */
  445.     public RefSpec expandFromDestination(Ref r) {
  446.         return expandFromDestination(r.getName());
  447.     }

  448.     private boolean match(String name, String s) {
  449.         if (s == null)
  450.             return false;
  451.         if (isWildcard(s)) {
  452.             int wildcardIndex = s.indexOf('*');
  453.             String prefix = s.substring(0, wildcardIndex);
  454.             String suffix = s.substring(wildcardIndex + 1);
  455.             return name.length() > prefix.length() + suffix.length()
  456.                     && name.startsWith(prefix) && name.endsWith(suffix);
  457.         }
  458.         return name.equals(s);
  459.     }

  460.     private static String expandWildcard(String name, String patternA,
  461.             String patternB) {
  462.         int a = patternA.indexOf('*');
  463.         int trailingA = patternA.length() - (a + 1);
  464.         int b = patternB.indexOf('*');
  465.         String match = name.substring(a, name.length() - trailingA);
  466.         return patternB.substring(0, b) + match + patternB.substring(b + 1);
  467.     }

  468.     private static String checkValid(String spec) {
  469.         if (spec != null && !isValid(spec))
  470.             throw new IllegalArgumentException(MessageFormat.format(
  471.                     JGitText.get().invalidRefSpec, spec));
  472.         return spec;
  473.     }

  474.     private static boolean isValid(String s) {
  475.         if (s.startsWith("/")) //$NON-NLS-1$
  476.             return false;
  477.         if (s.contains("//")) //$NON-NLS-1$
  478.             return false;
  479.         if (s.endsWith("/")) //$NON-NLS-1$
  480.             return false;
  481.         int i = s.indexOf('*');
  482.         if (i != -1) {
  483.             if (s.indexOf('*', i + 1) > i)
  484.                 return false;
  485.         }
  486.         return true;
  487.     }

  488.     /** {@inheritDoc} */
  489.     @Override
  490.     public int hashCode() {
  491.         int hc = 0;
  492.         if (getSource() != null)
  493.             hc = hc * 31 + getSource().hashCode();
  494.         if (getDestination() != null)
  495.             hc = hc * 31 + getDestination().hashCode();
  496.         return hc;
  497.     }

  498.     /** {@inheritDoc} */
  499.     @Override
  500.     public boolean equals(Object obj) {
  501.         if (!(obj instanceof RefSpec))
  502.             return false;
  503.         final RefSpec b = (RefSpec) obj;
  504.         if (isForceUpdate() != b.isForceUpdate())
  505.             return false;
  506.         if (isWildcard() != b.isWildcard())
  507.             return false;
  508.         if (!eq(getSource(), b.getSource()))
  509.             return false;
  510.         if (!eq(getDestination(), b.getDestination()))
  511.             return false;
  512.         return true;
  513.     }

  514.     private static boolean eq(String a, String b) {
  515.         if (References.isSameObject(a, b)) {
  516.             return true;
  517.         }
  518.         if (a == null || b == null)
  519.             return false;
  520.         return a.equals(b);
  521.     }

  522.     /** {@inheritDoc} */
  523.     @Override
  524.     public String toString() {
  525.         final StringBuilder r = new StringBuilder();
  526.         if (isForceUpdate())
  527.             r.append('+');
  528.         if (getSource() != null)
  529.             r.append(getSource());
  530.         if (getDestination() != null) {
  531.             r.append(':');
  532.             r.append(getDestination());
  533.         }
  534.         return r.toString();
  535.     }
  536. }