RemoteRefUpdate.java

  1. /*
  2.  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> 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.IOException;
  12. import java.text.MessageFormat;
  13. import java.util.Collection;

  14. import org.eclipse.jgit.annotations.NonNull;
  15. import org.eclipse.jgit.internal.JGitText;
  16. import org.eclipse.jgit.lib.ObjectId;
  17. import org.eclipse.jgit.lib.Ref;
  18. import org.eclipse.jgit.lib.RefUpdate;
  19. import org.eclipse.jgit.lib.Repository;
  20. import org.eclipse.jgit.revwalk.RevWalk;

  21. /**
  22.  * Represent request and status of a remote ref update. Specification is
  23.  * provided by client, while status is handled by
  24.  * {@link org.eclipse.jgit.transport.PushProcess} class, being read-only for
  25.  * client.
  26.  * <p>
  27.  * Client can create instances of this class directly, basing on user
  28.  * specification and advertised refs
  29.  * ({@link org.eclipse.jgit.transport.Connection} or through
  30.  * {@link org.eclipse.jgit.transport.Transport} helper methods. Apply this
  31.  * specification on remote repository using
  32.  * {@link org.eclipse.jgit.transport.Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)}
  33.  * method.
  34.  * </p>
  35.  */
  36. public class RemoteRefUpdate {
  37.     /**
  38.      * Represent current status of a remote ref update.
  39.      */
  40.     public enum Status {
  41.         /**
  42.          * Push process hasn't yet attempted to update this ref. This is the
  43.          * default status, prior to push process execution.
  44.          */
  45.         NOT_ATTEMPTED,

  46.         /**
  47.          * Remote ref was up to date, there was no need to update anything.
  48.          */
  49.         UP_TO_DATE,

  50.         /**
  51.          * Remote ref update was rejected, as it would cause non fast-forward
  52.          * update.
  53.          */
  54.         REJECTED_NONFASTFORWARD,

  55.         /**
  56.          * Remote ref update was rejected, because remote side doesn't
  57.          * support/allow deleting refs.
  58.          */
  59.         REJECTED_NODELETE,

  60.         /**
  61.          * Remote ref update was rejected, because old object id on remote
  62.          * repository wasn't the same as defined expected old object.
  63.          */
  64.         REJECTED_REMOTE_CHANGED,

  65.         /**
  66.          * Remote ref update was rejected for other reason, possibly described
  67.          * in {@link RemoteRefUpdate#getMessage()}.
  68.          */
  69.         REJECTED_OTHER_REASON,

  70.         /**
  71.          * Remote ref didn't exist. Can occur on delete request of a non
  72.          * existing ref.
  73.          */
  74.         NON_EXISTING,

  75.         /**
  76.          * Push process is awaiting update report from remote repository. This
  77.          * is a temporary state or state after critical error in push process.
  78.          */
  79.         AWAITING_REPORT,

  80.         /**
  81.          * Remote ref was successfully updated.
  82.          */
  83.         OK;
  84.     }

  85.     private ObjectId expectedOldObjectId;

  86.     private final ObjectId newObjectId;

  87.     private final String remoteName;

  88.     private final TrackingRefUpdate trackingRefUpdate;

  89.     private final String srcRef;

  90.     private final boolean forceUpdate;

  91.     private Status status;

  92.     private boolean fastForward;

  93.     private String message;

  94.     private final Repository localDb;

  95.     private RefUpdate localUpdate;

  96.     /**
  97.      * If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec
  98.      * to be expanded after the advertisements have been received in a push.
  99.      */
  100.     private Collection<RefSpec> fetchSpecs;

  101.     /**
  102.      * Construct remote ref update request by providing an update specification.
  103.      * Object is created with default
  104.      * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
  105.      * status and no message.
  106.      *
  107.      * @param localDb
  108.      *            local repository to push from.
  109.      * @param srcRef
  110.      *            source revision - any string resolvable by
  111.      *            {@link org.eclipse.jgit.lib.Repository#resolve(String)}. This
  112.      *            resolves to the new object that the caller want remote ref to
  113.      *            be after update. Use null or
  114.      *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} string for
  115.      *            delete request.
  116.      * @param remoteName
  117.      *            full name of a remote ref to update, e.g. "refs/heads/master"
  118.      *            (no wildcard, no short name).
  119.      * @param forceUpdate
  120.      *            true when caller want remote ref to be updated regardless
  121.      *            whether it is fast-forward update (old object is ancestor of
  122.      *            new object).
  123.      * @param localName
  124.      *            optional full name of a local stored tracking branch, to
  125.      *            update after push, e.g. "refs/remotes/zawir/dirty" (no
  126.      *            wildcard, no short name); null if no local tracking branch
  127.      *            should be updated.
  128.      * @param expectedOldObjectId
  129.      *            optional object id that caller is expecting, requiring to be
  130.      *            advertised by remote side before update; update will take
  131.      *            place ONLY if remote side advertise exactly this expected id;
  132.      *            null if caller doesn't care what object id remote side
  133.      *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
  134.      *            when expecting no remote ref with this name.
  135.      * @throws java.io.IOException
  136.      *             when I/O error occurred during creating
  137.      *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
  138.      *             local tracking branch or srcRef can't be resolved to any
  139.      *             object.
  140.      * @throws java.lang.IllegalArgumentException
  141.      *             if some required parameter was null
  142.      */
  143.     public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName,
  144.             boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
  145.             throws IOException {
  146.         this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
  147.                 : ObjectId.zeroId(), remoteName, forceUpdate, localName,
  148.                 expectedOldObjectId);
  149.     }

  150.     /**
  151.      * Construct remote ref update request by providing an update specification.
  152.      * Object is created with default
  153.      * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
  154.      * status and no message.
  155.      *
  156.      * @param localDb
  157.      *            local repository to push from.
  158.      * @param srcRef
  159.      *            source revision. Use null to delete.
  160.      * @param remoteName
  161.      *            full name of a remote ref to update, e.g. "refs/heads/master"
  162.      *            (no wildcard, no short name).
  163.      * @param forceUpdate
  164.      *            true when caller want remote ref to be updated regardless
  165.      *            whether it is fast-forward update (old object is ancestor of
  166.      *            new object).
  167.      * @param localName
  168.      *            optional full name of a local stored tracking branch, to
  169.      *            update after push, e.g. "refs/remotes/zawir/dirty" (no
  170.      *            wildcard, no short name); null if no local tracking branch
  171.      *            should be updated.
  172.      * @param expectedOldObjectId
  173.      *            optional object id that caller is expecting, requiring to be
  174.      *            advertised by remote side before update; update will take
  175.      *            place ONLY if remote side advertise exactly this expected id;
  176.      *            null if caller doesn't care what object id remote side
  177.      *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
  178.      *            when expecting no remote ref with this name.
  179.      * @throws java.io.IOException
  180.      *             when I/O error occurred during creating
  181.      *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
  182.      *             local tracking branch or srcRef can't be resolved to any
  183.      *             object.
  184.      * @throws java.lang.IllegalArgumentException
  185.      *             if some required parameter was null
  186.      */
  187.     public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName,
  188.             boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
  189.             throws IOException {
  190.         this(localDb, srcRef != null ? srcRef.getName() : null,
  191.                 srcRef != null ? srcRef.getObjectId() : null, remoteName,
  192.                 forceUpdate, localName, expectedOldObjectId);
  193.     }

  194.     /**
  195.      * Construct remote ref update request by providing an update specification.
  196.      * Object is created with default
  197.      * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
  198.      * status and no message.
  199.      *
  200.      * @param localDb
  201.      *            local repository to push from.
  202.      * @param srcRef
  203.      *            source revision to label srcId with. If null srcId.name() will
  204.      *            be used instead.
  205.      * @param srcId
  206.      *            The new object that the caller wants remote ref to be after
  207.      *            update. Use null or
  208.      *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} for delete
  209.      *            request.
  210.      * @param remoteName
  211.      *            full name of a remote ref to update, e.g. "refs/heads/master"
  212.      *            (no wildcard, no short name).
  213.      * @param forceUpdate
  214.      *            true when caller want remote ref to be updated regardless
  215.      *            whether it is fast-forward update (old object is ancestor of
  216.      *            new object).
  217.      * @param localName
  218.      *            optional full name of a local stored tracking branch, to
  219.      *            update after push, e.g. "refs/remotes/zawir/dirty" (no
  220.      *            wildcard, no short name); null if no local tracking branch
  221.      *            should be updated.
  222.      * @param expectedOldObjectId
  223.      *            optional object id that caller is expecting, requiring to be
  224.      *            advertised by remote side before update; update will take
  225.      *            place ONLY if remote side advertise exactly this expected id;
  226.      *            null if caller doesn't care what object id remote side
  227.      *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
  228.      *            when expecting no remote ref with this name.
  229.      * @throws java.io.IOException
  230.      *             when I/O error occurred during creating
  231.      *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
  232.      *             local tracking branch or srcRef can't be resolved to any
  233.      *             object.
  234.      * @throws java.lang.IllegalArgumentException
  235.      *             if some required parameter was null
  236.      */
  237.     public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
  238.             String remoteName, boolean forceUpdate, String localName,
  239.             ObjectId expectedOldObjectId) throws IOException {
  240.         this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
  241.                 expectedOldObjectId);
  242.     }

  243.     private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
  244.             String remoteName, boolean forceUpdate, String localName,
  245.             Collection<RefSpec> fetchSpecs, ObjectId expectedOldObjectId)
  246.             throws IOException {
  247.         if (fetchSpecs == null) {
  248.             if (remoteName == null) {
  249.                 throw new IllegalArgumentException(
  250.                         JGitText.get().remoteNameCannotBeNull);
  251.             }
  252.             if (srcId == null && srcRef != null) {
  253.                 throw new IOException(MessageFormat.format(
  254.                         JGitText.get().sourceRefDoesntResolveToAnyObject,
  255.                         srcRef));
  256.             }
  257.         }
  258.         if (srcRef != null) {
  259.             this.srcRef = srcRef;
  260.         } else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
  261.             this.srcRef = srcId.name();
  262.         } else {
  263.             this.srcRef = null;
  264.         }
  265.         if (srcId != null) {
  266.             this.newObjectId = srcId;
  267.         } else {
  268.             this.newObjectId = ObjectId.zeroId();
  269.         }
  270.         this.fetchSpecs = fetchSpecs;
  271.         this.remoteName = remoteName;
  272.         this.forceUpdate = forceUpdate;
  273.         if (localName != null && localDb != null) {
  274.             localUpdate = localDb.updateRef(localName);
  275.             localUpdate.setForceUpdate(true);
  276.             localUpdate.setRefLogMessage("push", true); //$NON-NLS-1$
  277.             localUpdate.setNewObjectId(newObjectId);
  278.             trackingRefUpdate = new TrackingRefUpdate(
  279.                     true,
  280.                     remoteName,
  281.                     localName,
  282.                     localUpdate.getOldObjectId() != null
  283.                         ? localUpdate.getOldObjectId()
  284.                         : ObjectId.zeroId(),
  285.                     newObjectId);
  286.         } else {
  287.             trackingRefUpdate = null;
  288.         }
  289.         this.localDb = localDb;
  290.         this.expectedOldObjectId = expectedOldObjectId;
  291.         this.status = Status.NOT_ATTEMPTED;
  292.     }

  293.     /**
  294.      * Create a new instance of this object basing on existing instance for
  295.      * configuration. State (like {@link #getMessage()}, {@link #getStatus()})
  296.      * of base object is not shared. Expected old object id is set up from
  297.      * scratch, as this constructor may be used for 2-stage push: first one
  298.      * being dry run, second one being actual push.
  299.      *
  300.      * @param base
  301.      *            configuration base.
  302.      * @param newExpectedOldObjectId
  303.      *            new expected object id value.
  304.      * @throws java.io.IOException
  305.      *             when I/O error occurred during creating
  306.      *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
  307.      *             local tracking branch or srcRef of base object no longer can
  308.      *             be resolved to any object.
  309.      */
  310.     public RemoteRefUpdate(RemoteRefUpdate base,
  311.             ObjectId newExpectedOldObjectId) throws IOException {
  312.         this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
  313.                 base.forceUpdate,
  314.                 (base.trackingRefUpdate == null ? null : base.trackingRefUpdate
  315.                         .getLocalName()),
  316.                 base.fetchSpecs, newExpectedOldObjectId);
  317.     }

  318.     /**
  319.      * Creates a "placeholder" update for the "matching" RefSpec ":".
  320.      *
  321.      * @param localDb
  322.      *            local repository to push from
  323.      * @param forceUpdate
  324.      *            whether non-fast-forward updates shall be allowed
  325.      * @param fetchSpecs
  326.      *            The fetch {@link RefSpec}s to use when this placeholder is
  327.      *            expanded to determine remote tracking branch updates
  328.      */
  329.     RemoteRefUpdate(Repository localDb, boolean forceUpdate,
  330.             @NonNull Collection<RefSpec> fetchSpecs) {
  331.         this.localDb = localDb;
  332.         this.forceUpdate = forceUpdate;
  333.         this.fetchSpecs = fetchSpecs;
  334.         this.trackingRefUpdate = null;
  335.         this.srcRef = null;
  336.         this.remoteName = null;
  337.         this.newObjectId = null;
  338.         this.status = Status.NOT_ATTEMPTED;
  339.     }

  340.     /**
  341.      * Tells whether this {@link RemoteRefUpdate} is a placeholder for a
  342.      * "matching" {@link RefSpec}.
  343.      *
  344.      * @return {@code true} if this is a placeholder, {@code false} otherwise
  345.      * @since 6.1
  346.      */
  347.     public boolean isMatching() {
  348.         return fetchSpecs != null;
  349.     }

  350.     /**
  351.      * Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}.
  352.      *
  353.      * @return the fetch {@link RefSpec}s, or {@code null} if
  354.      *         {@code this.}{@link #isMatching()} {@code == false}
  355.      */
  356.     Collection<RefSpec> getFetchSpecs() {
  357.         return fetchSpecs;
  358.     }

  359.     /**
  360.      * Get expected old object id
  361.      *
  362.      * @return expectedOldObjectId required to be advertised by remote side, as
  363.      *         set in constructor; may be null.
  364.      */
  365.     public ObjectId getExpectedOldObjectId() {
  366.         return expectedOldObjectId;
  367.     }

  368.     /**
  369.      * Whether some object is required to be advertised by remote side, as set
  370.      * in constructor
  371.      *
  372.      * @return true if some object is required to be advertised by remote side,
  373.      *         as set in constructor; false otherwise.
  374.      */
  375.     public boolean isExpectingOldObjectId() {
  376.         return expectedOldObjectId != null;
  377.     }

  378.     /**
  379.      * Get new object id
  380.      *
  381.      * @return newObjectId for remote ref, as set in constructor.
  382.      */
  383.     public ObjectId getNewObjectId() {
  384.         return newObjectId;
  385.     }

  386.     /**
  387.      * Whether this update is a deleting update
  388.      *
  389.      * @return true if this update is deleting update; false otherwise.
  390.      */
  391.     public boolean isDelete() {
  392.         return ObjectId.zeroId().equals(newObjectId);
  393.     }

  394.     /**
  395.      * Get name of remote ref to update
  396.      *
  397.      * @return name of remote ref to update, as set in constructor.
  398.      */
  399.     public String getRemoteName() {
  400.         return remoteName;
  401.     }

  402.     /**
  403.      * Get tracking branch update if localName was set in constructor.
  404.      *
  405.      * @return local tracking branch update if localName was set in constructor.
  406.      */
  407.     public TrackingRefUpdate getTrackingRefUpdate() {
  408.         return trackingRefUpdate;
  409.     }

  410.     /**
  411.      * Get source revision as specified by user (in constructor)
  412.      *
  413.      * @return source revision as specified by user (in constructor), could be
  414.      *         any string parseable by
  415.      *         {@link org.eclipse.jgit.lib.Repository#resolve(String)}; can be
  416.      *         null if specified that way in constructor - this stands for
  417.      *         delete request.
  418.      */
  419.     public String getSrcRef() {
  420.         return srcRef;
  421.     }

  422.     /**
  423.      * Whether user specified a local tracking branch for remote update
  424.      *
  425.      * @return true if user specified a local tracking branch for remote update;
  426.      *         false otherwise.
  427.      */
  428.     public boolean hasTrackingRefUpdate() {
  429.         return trackingRefUpdate != null;
  430.     }

  431.     /**
  432.      * Whether this update is forced regardless of old remote ref object
  433.      *
  434.      * @return true if this update is forced regardless of old remote ref
  435.      *         object; false otherwise.
  436.      */
  437.     public boolean isForceUpdate() {
  438.         return forceUpdate;
  439.     }

  440.     /**
  441.      * Get status of remote ref update operation.
  442.      *
  443.      * @return status of remote ref update operation.
  444.      */
  445.     public Status getStatus() {
  446.         return status;
  447.     }

  448.     /**
  449.      * Check whether update was fast-forward. Note that this result is
  450.      * meaningful only after successful update (when status is
  451.      * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#OK}).
  452.      *
  453.      * @return true if update was fast-forward; false otherwise.
  454.      */
  455.     public boolean isFastForward() {
  456.         return fastForward;
  457.     }

  458.     /**
  459.      * Get message describing reasons of status when needed/possible; may be
  460.      * null.
  461.      *
  462.      * @return message describing reasons of status when needed/possible; may be
  463.      *         null.
  464.      */
  465.     public String getMessage() {
  466.         return message;
  467.     }

  468.     void setExpectedOldObjectId(ObjectId id) {
  469.         expectedOldObjectId = id;
  470.     }

  471.     void setStatus(Status status) {
  472.         this.status = status;
  473.     }

  474.     void setFastForward(boolean fastForward) {
  475.         this.fastForward = fastForward;
  476.     }

  477.     void setMessage(String message) {
  478.         this.message = message;
  479.     }

  480.     /**
  481.      * Update locally stored tracking branch with the new object.
  482.      *
  483.      * @param walk
  484.      *            walker used for checking update properties.
  485.      * @throws java.io.IOException
  486.      *             when I/O error occurred during update
  487.      */
  488.     protected void updateTrackingRef(RevWalk walk) throws IOException {
  489.         if (isDelete())
  490.             trackingRefUpdate.setResult(localUpdate.delete(walk));
  491.         else
  492.             trackingRefUpdate.setResult(localUpdate.update(walk));
  493.     }

  494.     /** {@inheritDoc} */
  495.     @SuppressWarnings("nls")
  496.     @Override
  497.     public String toString() {
  498.         return "RemoteRefUpdate[remoteName="
  499.                 + remoteName
  500.                 + ", "
  501.                 + status
  502.                 + ", "
  503.                 + (expectedOldObjectId != null ? expectedOldObjectId.name()
  504.                         : "(null)") + "..."
  505.                 + (newObjectId != null ? newObjectId.name() : "(null)")
  506.                 + (fastForward ? ", fastForward" : "")
  507.                 + ", srcRef=" + srcRef
  508.                 + (forceUpdate ? ", forceUpdate" : "") + ", message="
  509.                 + (message != null ? "\"" + message + "\"" : "null") + "]";
  510.     }
  511. }