diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java | 609 |
1 files changed, 531 insertions, 78 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java index 7c44dba4a2..bfc75f036c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -1,53 +1,26 @@ /* - * Copyright (C) 2008, Google Inc. - * and other copyright owners as documented in the project's IP log. + * Copyright (C) 2008, Google Inc. and others * - * 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 + * 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. * - * 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. + * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -58,14 +31,15 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** - * A command being processed by {@link BaseReceivePack}. + * A command being processed by + * {@link org.eclipse.jgit.transport.ReceivePack}. * <p> * This command instance roughly translates to the server side representation of - * the {@link RemoteRefUpdate} created by the client. + * the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client. */ public class ReceiveCommand { /** Type of operation requested. */ - public static enum Type { + public enum Type { /** Create a new ref; the ref must not already exist. */ CREATE, @@ -91,7 +65,7 @@ public class ReceiveCommand { } /** Result of the update command. */ - public static enum Result { + public enum Result { /** The command has not yet been attempted by the server. */ NOT_ATTEMPTED, @@ -127,6 +101,31 @@ public class ReceiveCommand { } /** + * Filter a collection of commands according to result. + * + * @param in + * commands to filter. + * @param want + * desired status to filter by. + * @return a copy of the command list containing only those commands with + * the desired status. + * @since 4.2 + */ + public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in, + Result want) { + List<ReceiveCommand> r; + if (in instanceof Collection) + r = new ArrayList<>(((Collection<?>) in).size()); + else + r = new ArrayList<>(); + for (ReceiveCommand cmd : in) { + if (cmd.getResult() == want) + r.add(cmd); + } + return r; + } + + /** * Filter a list of commands according to result. * * @param commands @@ -138,121 +137,550 @@ public class ReceiveCommand { * @since 2.0 */ public static List<ReceiveCommand> filter(List<ReceiveCommand> commands, - final Result want) { - List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands.size()); - for (final ReceiveCommand cmd : commands) { - if (cmd.getResult() == want) - r.add(cmd); + Result want) { + return filter((Iterable<ReceiveCommand>) commands, want); + } + + /** + * Set unprocessed commands as failed due to transaction aborted. + * <p> + * If a command is still + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} it + * will be set to + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}. + * + * @param commands + * commands to mark as failed. + * @since 4.2 + */ + public static void abort(Iterable<ReceiveCommand> commands) { + for (ReceiveCommand c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, + JGitText.get().transactionAborted); + } } - return r; + } + + /** + * Check whether a command failed due to transaction aborted. + * + * @param cmd + * command. + * @return whether the command failed due to transaction aborted, as in + * {@link #abort(Iterable)}. + * @since 4.9 + */ + public static boolean isTransactionAborted(ReceiveCommand cmd) { + return cmd.getResult() == REJECTED_OTHER_REASON + && cmd.getMessage().equals(JGitText.get().transactionAborted); + } + + /** + * Create a command to switch a reference from object to symbolic. + * + * @param oldId + * expected oldId. May be {@code zeroId} to create. + * @param newTarget + * new target; must begin with {@code "refs/"}. + * @param name + * name of the reference to make symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand link(@NonNull ObjectId oldId, + @NonNull String newTarget, @NonNull String name) { + return new ReceiveCommand(oldId, newTarget, name); + } + + /** + * Create a command to switch a symbolic reference's target. + * + * @param oldTarget + * expected old target. May be null to create. + * @param newTarget + * new target; must begin with {@code "refs/"}. + * @param name + * name of the reference to make symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand link(@Nullable String oldTarget, + @NonNull String newTarget, @NonNull String name) { + return new ReceiveCommand(oldTarget, newTarget, name); + } + + /** + * Create a command to switch a reference from symbolic to object. + * + * @param oldTarget + * expected old target. + * @param newId + * new object identifier. May be {@code zeroId()} to delete. + * @param name + * name of the reference to convert from symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand unlink(@NonNull String oldTarget, + @NonNull ObjectId newId, @NonNull String name) { + return new ReceiveCommand(oldTarget, newId, name); } private final ObjectId oldId; + private final String oldSymref; + private final ObjectId newId; + private final String newSymref; + private final String name; private Type type; + private boolean typeIsCorrect; + private Ref ref; private Result status = Result.NOT_ATTEMPTED; private String message; - private boolean typeIsCorrect; + private boolean customRefLog; + + private String refLogMessage; + + private boolean refLogIncludeResult; + + private Boolean forceRefLog; /** - * Create a new command for {@link BaseReceivePack}. + * Create a new command for + * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId - * the old object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref creation. + * the expected old object id; must not be null. Use + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref creation. * @param newId * the new object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref deletion. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref deletion. * @param name * name of the ref being affected. */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } this.oldId = oldId; + this.oldSymref = null; this.newId = newId; + this.newSymref = null; this.name = name; type = Type.UPDATE; - if (ObjectId.zeroId().equals(oldId)) + if (ObjectId.zeroId().equals(oldId)) { type = Type.CREATE; - if (ObjectId.zeroId().equals(newId)) + } + if (ObjectId.zeroId().equals(newId)) { type = Type.DELETE; + } } /** - * Create a new command for {@link BaseReceivePack}. + * Create a new command for + * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId * the old object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref creation. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref creation. * @param newId * the new object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref deletion. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref deletion. * @param name * name of the ref being affected. * @param type - * type of the command. + * type of the command. Must be + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#CREATE} + * if {@code + * oldId} is zero, or + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#DELETE} + * if {@code newId} is zero. * @since 2.0 */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name, final Type type) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } this.oldId = oldId; + this.oldSymref = null; this.newId = newId; + this.newSymref = null; this.name = name; + switch (type) { + case CREATE: + if (!ObjectId.zeroId().equals(oldId)) { + throw new IllegalArgumentException( + JGitText.get().createRequiresZeroOldId); + } + break; + case DELETE: + if (!ObjectId.zeroId().equals(newId)) { + throw new IllegalArgumentException( + JGitText.get().deleteRequiresZeroNewId); + } + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + if (ObjectId.zeroId().equals(newId) + || ObjectId.zeroId().equals(oldId)) { + throw new IllegalArgumentException( + JGitText.get().updateRequiresOldIdAndNewId); + } + break; + default: + throw new IllegalStateException( + JGitText.get().enumValueNotSupported0); + } this.type = type; } - /** @return the old value the client thinks the ref has. */ + /** + * Create a command to switch a reference from object to symbolic. + * + * @param oldId + * the old object id; must not be null. Use + * {@link ObjectId#zeroId()} to indicate a ref creation. + * @param newSymref + * new target, must begin with {@code "refs/"}. Use {@code null} + * to indicate a ref deletion. + * @param name + * name of the reference to make symbolic. + * @since 4.10 + */ + private ReceiveCommand(ObjectId oldId, String newSymref, String name) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = oldId; + this.oldSymref = null; + this.newId = ObjectId.zeroId(); + this.newSymref = newSymref; + this.name = name; + if (AnyObjectId.isEqual(ObjectId.zeroId(), oldId)) { + type = Type.CREATE; + } else if (newSymref != null) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Create a command to switch a reference from symbolic to object. + * + * @param oldSymref + * expected old target. Use {@code null} to indicate a ref + * creation. + * @param newId + * the new object id; must not be null. Use + * {@link ObjectId#zeroId()} to indicate a ref deletion. + * @param name + * name of the reference to convert from symbolic. + * @since 4.10 + */ + private ReceiveCommand(String oldSymref, ObjectId newId, String name) { + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = ObjectId.zeroId(); + this.oldSymref = oldSymref; + this.newId = newId; + this.newSymref = null; + this.name = name; + if (oldSymref == null) { + type = Type.CREATE; + } else if (!AnyObjectId.isEqual(ObjectId.zeroId(), newId)) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Create a command to switch a symbolic reference's target. + * + * @param oldTarget + * expected old target. Use {@code null} to indicate a ref + * creation. + * @param newTarget + * new target. Use {@code null} to indicate a ref deletion. + * @param name + * name of the reference to make symbolic. + * @since 4.10 + */ + private ReceiveCommand(@Nullable String oldTarget, String newTarget, String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = ObjectId.zeroId(); + this.oldSymref = oldTarget; + this.newId = ObjectId.zeroId(); + this.newSymref = newTarget; + this.name = name; + if (oldTarget == null) { + if (newTarget == null) { + throw new IllegalArgumentException( + JGitText.get().bothRefTargetsMustNotBeNull); + } + type = Type.CREATE; + } else if (newTarget != null) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Get the old value the client thinks the ref has. + * + * @return the old value the client thinks the ref has. + */ public ObjectId getOldId() { return oldId; } - /** @return the requested new value for this ref. */ + /** + * Get expected old target for a symbolic reference. + * + * @return expected old target for a symbolic reference. + * @since 4.10 + */ + @Nullable + public String getOldSymref() { + return oldSymref; + } + + /** + * Get the requested new value for this ref. + * + * @return the requested new value for this ref. + */ public ObjectId getNewId() { return newId; } - /** @return the name of the ref being updated. */ + /** + * Get requested new target for a symbolic reference. + * + * @return requested new target for a symbolic reference. + * @since 4.10 + */ + @Nullable + public String getNewSymref() { + return newSymref; + } + + /** + * Get the name of the ref being updated. + * + * @return the name of the ref being updated. + */ public String getRefName() { return name; } - /** @return the type of this command; see {@link Type}. */ + /** + * Get the type of this command; see {@link Type}. + * + * @return the type of this command; see {@link Type}. + */ public Type getType() { return type; } - /** @return the ref, if this was advertised by the connection. */ + /** + * Get the ref, if this was advertised by the connection. + * + * @return the ref, if this was advertised by the connection. + */ public Ref getRef() { return ref; } - /** @return the current status code of this command. */ + /** + * Get the current status code of this command. + * + * @return the current status code of this command. + */ public Result getResult() { return status; } - /** @return the message associated with a failure status. */ + /** + * Get the message associated with a failure status. + * + * @return the message associated with a failure status. + */ public String getMessage() { return message; } /** + * Set the message to include in the reflog. + * <p> + * Overrides the default set by {@code setRefLogMessage} on any containing + * {@link org.eclipse.jgit.lib.BatchRefUpdate}. + * + * @param msg + * the message to describe this change. If null and appendStatus is + * false, the reflog will not be updated. + * @param appendStatus + * true if the status of the ref change (fast-forward or + * forced-update) should be appended to the user supplied message. + * @since 4.9 + */ + public void setRefLogMessage(String msg, boolean appendStatus) { + customRefLog = true; + if (msg == null && !appendStatus) { + disableRefLog(); + } else if (msg == null && appendStatus) { + refLogMessage = ""; //$NON-NLS-1$ + refLogIncludeResult = true; + } else { + refLogMessage = msg; + refLogIncludeResult = appendStatus; + } + } + + /** + * Don't record this update in the ref's associated reflog. + * <p> + * Equivalent to {@code setRefLogMessage(null, false)}. + * + * @since 4.9 + */ + public void disableRefLog() { + customRefLog = true; + refLogMessage = null; + refLogIncludeResult = false; + } + + /** + * Force writing a reflog for the updated ref. + * + * @param force whether to force. + * @since 4.9 + */ + public void setForceRefLog(boolean force) { + forceRefLog = Boolean.valueOf(force); + } + + /** + * Check whether this command has a custom reflog message setting that should + * override defaults in any containing + * {@link org.eclipse.jgit.lib.BatchRefUpdate}. + * <p> + * Does not take into account whether {@code #setForceRefLog(boolean)} has + * been called. + * + * @return whether a custom reflog is set. + * @since 4.9 + */ + public boolean hasCustomRefLog() { + return customRefLog; + } + + /** + * Check whether log has been disabled by {@link #disableRefLog()}. + * + * @return true if disabled. + * @since 4.9 + */ + public boolean isRefLogDisabled() { + return refLogMessage == null; + } + + /** + * Get the message to include in the reflog. + * + * @return message the caller wants to include in the reflog; null if the + * update should not be logged. + * @since 4.9 + */ + @Nullable + public String getRefLogMessage() { + return refLogMessage; + } + + /** + * Check whether the reflog message should include the result of the update, + * such as fast-forward or force-update. + * + * @return true if the message should include the result. + * @since 4.9 + */ + public boolean isRefLogIncludingResult() { + return refLogIncludeResult; + } + + /** + * Check whether the reflog should be written regardless of repo defaults. + * + * @return whether force writing is enabled; {@code null} if + * {@code #setForceRefLog(boolean)} was never called. + * @since 4.9 + */ + @Nullable + public Boolean isForceRefLog() { + return forceRefLog; + } + + /** * Set the status of this command. * * @param s * the new status code for this command. */ - public void setResult(final Result s) { + public void setResult(Result s) { setResult(s, null); } @@ -264,7 +692,7 @@ public class ReceiveCommand { * @param m * optional message explaining the new status. */ - public void setResult(final Result s, final String m) { + public void setResult(Result s, String m) { status = s; message = m; } @@ -275,12 +703,13 @@ public class ReceiveCommand { * If the command's current type is UPDATE, a merge test will be performed * using the supplied RevWalk to determine if {@link #getOldId()} is fully * merged into {@link #getNewId()}. If some commits are not merged the - * update type is changed to {@link Type#UPDATE_NONFASTFORWARD}. + * update type is changed to + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD}. * * @param walk * an instance to perform the merge test with. The caller must * allocate and release this object. - * @throws IOException + * @throws java.io.IOException * either oldId or newId is not accessible in the repository * used by the RevWalk. This usually indicates data corruption, * and the command cannot be processed. @@ -288,7 +717,7 @@ public class ReceiveCommand { public void updateType(RevWalk walk) throws IOException { if (typeIsCorrect) return; - if (type == Type.UPDATE && !AnyObjectId.equals(oldId, newId)) { + if (type == Type.UPDATE && !AnyObjectId.isEqual(oldId, newId)) { RevObject o = walk.parseAny(oldId); RevObject n = walk.parseAny(newId); if (!(o instanceof RevCommit) @@ -306,12 +735,24 @@ public class ReceiveCommand { * * @param rp * receive-pack session. - * @since 2.0 + * @since 5.6 */ - public void execute(final BaseReceivePack rp) { + public void execute(ReceivePack rp) { try { - final RefUpdate ru = rp.getRepository().updateRef(getRefName()); + String expTarget = getOldSymref(); + boolean detach = getNewSymref() != null + || (type == Type.DELETE && expTarget != null); + RefUpdate ru = rp.getRepository().updateRef(getRefName(), detach); + if (expTarget != null) { + if (!ru.getRef().isSymbolic() || !ru.getRef().getTarget() + .getName().equals(expTarget)) { + setResult(Result.LOCK_FAILURE); + return; + } + } + ru.setRefLogIdent(rp.getRefLogIdent()); + ru.setRefLogMessage(refLogMessage, refLogIncludeResult); switch (getType()) { case DELETE: if (!ObjectId.zeroId().equals(getOldId())) { @@ -330,9 +771,13 @@ public class ReceiveCommand { case UPDATE_NONFASTFORWARD: ru.setForceUpdate(rp.isAllowNonFastForwards()); ru.setExpectedOldObjectId(getOldId()); - ru.setNewObjectId(getNewId()); ru.setRefLogMessage("push", true); //$NON-NLS-1$ - setResult(ru.update(rp.getRevWalk())); + if (getNewSymref() != null) { + setResult(ru.link(getNewSymref())); + } else { + ru.setNewObjectId(getNewId()); + setResult(ru.update(rp.getRevWalk())); + } break; } } catch (IOException err) { @@ -340,11 +785,11 @@ public class ReceiveCommand { } } - void setRef(final Ref r) { + void setRef(Ref r) { ref = r; } - void setType(final Type t) { + void setType(Type t) { type = t; } @@ -385,6 +830,14 @@ public class ReceiveCommand { setResult(Result.REJECTED_CURRENT_BRANCH); break; + case REJECTED_MISSING_OBJECT: + setResult(Result.REJECTED_MISSING_OBJECT); + break; + + case REJECTED_OTHER_REASON: + setResult(Result.REJECTED_OTHER_REASON); + break; + default: setResult(Result.REJECTED_OTHER_REASON, r.name()); break; |