summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java363
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java22
12 files changed, 611 insertions, 6 deletions
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 33ea1deb8e..1a4b5525b6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -108,6 +108,9 @@ class Push extends TextBuiltin {
@Option(name = "--dry-run")
private boolean dryRun;
+ @Option(name = "--push-option", aliases = { "-t" })
+ private List<String> pushOptions = new ArrayList<>();
+
private boolean shownURI;
@Override
@@ -127,6 +130,7 @@ class Push extends TextBuiltin {
push.setThin(thin);
push.setAtomic(atomic);
push.setTimeout(timeout);
+ push.setPushOptions(pushOptions);
Iterable<PushResult> results = push.call();
for (PushResult result : results) {
try (ObjectReader reader = db.newObjectReader()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
new file mode 100644
index 0000000000..1554f84305
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2016, Google Inc.
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.PushCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushOptionsTest extends RepositoryTestCase {
+ private URIish uri;
+ private TestProtocol<Object> testProtocol;
+ private Object ctx = new Object();
+ private InMemoryRepository server;
+ private InMemoryRepository client;
+ private ObjectId obj1;
+ private ObjectId obj2;
+ private BaseReceivePack baseReceivePack;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ server = newRepo("server");
+ client = newRepo("client");
+
+ testProtocol = new TestProtocol<>(null,
+ new ReceivePackFactory<Object>() {
+ @Override
+ public ReceivePack create(Object req, Repository database)
+ throws ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ ReceivePack receivePack = new ReceivePack(database);
+ receivePack.setAllowPushOptions(true);
+ receivePack.setAtomic(true);
+ baseReceivePack = receivePack;
+ return receivePack;
+ }
+ });
+
+ uri = testProtocol.register(ctx, server);
+
+ try (ObjectInserter ins = client.newObjectInserter()) {
+ obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
+ obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
+ ins.flush();
+ }
+ }
+
+ @After
+ public void tearDown() {
+ baseReceivePack = null;
+ Transport.unregister(testProtocol);
+ }
+
+ private static InMemoryRepository newRepo(String name) {
+ return new InMemoryRepository(new DfsRepositoryDescription(name));
+ }
+
+ private List<RemoteRefUpdate> commands(boolean atomicSafe)
+ throws IOException {
+ List<RemoteRefUpdate> cmds = new ArrayList<>();
+ cmds.add(new RemoteRefUpdate(null, null, obj1, "refs/heads/one",
+ true /* force update */, null /* no local tracking ref */,
+ ObjectId.zeroId()));
+ cmds.add(new RemoteRefUpdate(null, null, obj2, "refs/heads/two",
+ true /* force update */, null /* no local tracking ref */,
+ atomicSafe ? ObjectId.zeroId() : obj1));
+ return cmds;
+ }
+
+ private void connectLocalToRemote(Git local, Git remote)
+ throws URISyntaxException, IOException {
+ StoredConfig config = local.getRepository().getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+ remoteConfig.addURI(new URIish(
+ remote.getRepository().getDirectory().toURI().toURL()));
+ remoteConfig.addFetchRefSpec(
+ new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+ remoteConfig.update(config);
+ config.save();
+ }
+
+ private RevCommit addCommit(Git git)
+ throws IOException, NoFilepatternException, GitAPIException {
+ writeTrashFile("f", "content of f");
+ git.add().addFilepattern("f").call();
+ return git.commit().setMessage("adding f").call();
+ }
+
+ @Test
+ public void testNonAtomicPushWithOptions() throws Exception {
+ PushResult r;
+ server.setPerformsAtomicTransactions(false);
+ List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.setPushAtomic(false);
+ tn.setPushOptions(pushOptions);
+
+ r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+ }
+
+ RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+ RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+ assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+ assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+ two.getStatus());
+ assertEquals(pushOptions, baseReceivePack.getPushOptions());
+ }
+
+ @Test
+ public void testAtomicPushWithOptions() throws Exception {
+ PushResult r;
+ server.setPerformsAtomicTransactions(true);
+ List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.setPushAtomic(true);
+ tn.setPushOptions(pushOptions);
+
+ r = tn.push(NullProgressMonitor.INSTANCE, commands(true));
+ }
+
+ RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+ RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+ assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+ assertSame(RemoteRefUpdate.Status.OK, two.getStatus());
+ assertEquals(pushOptions, baseReceivePack.getPushOptions());
+ }
+
+ @Test
+ public void testFailedAtomicPushWithOptions() throws Exception {
+ PushResult r;
+ server.setPerformsAtomicTransactions(true);
+ List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.setPushAtomic(true);
+ tn.setPushOptions(pushOptions);
+
+ r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+ }
+
+ RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+ RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+ assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
+ one.getStatus());
+ assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+ two.getStatus());
+ assertEquals(new ArrayList<String>(), baseReceivePack.getPushOptions());
+ }
+
+ @Test
+ public void testThinPushWithOptions() throws Exception {
+ PushResult r;
+ List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.setPushThin(true);
+ tn.setPushOptions(pushOptions);
+
+ r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+ }
+
+ RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+ RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+ assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+ assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+ two.getStatus());
+ assertEquals(pushOptions, baseReceivePack.getPushOptions());
+ }
+
+ @Test
+ public void testPushWithoutOptions() throws Exception {
+ try (Git local = new Git(db);
+ Git remote = new Git(createBareRepository())) {
+ connectLocalToRemote(local, remote);
+
+ final StoredConfig config2 = remote.getRepository().getConfig();
+ config2.setBoolean("receive", null, "pushoptions", true);
+ config2.save();
+
+ RevCommit commit = addCommit(local);
+
+ local.checkout().setName("not-pushed").setCreateBranch(true).call();
+ local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+ assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+ PushCommand pushCommand = local.push().setRemote("test");
+ pushCommand.call();
+
+ assertEquals(commit.getId(),
+ remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+ }
+ }
+
+ @Test
+ public void testPushWithEmptyOptions() throws Exception {
+ try (Git local = new Git(db);
+ Git remote = new Git(createBareRepository())) {
+ connectLocalToRemote(local, remote);
+
+ final StoredConfig config2 = remote.getRepository().getConfig();
+ config2.setBoolean("receive", null, "pushoptions", true);
+ config2.save();
+
+ RevCommit commit = addCommit(local);
+
+ local.checkout().setName("not-pushed").setCreateBranch(true).call();
+ local.checkout().setName("branchtopush").setCreateBranch(true).call();
+ assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+ List<String> pushOptions = new ArrayList<>();
+ PushCommand pushCommand = local.push().setRemote("test")
+ .setPushOptions(pushOptions);
+ pushCommand.call();
+
+ assertEquals(commit.getId(),
+ remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+ }
+ }
+
+ @Test
+ public void testAdvertisedButUnusedPushOptions() throws Exception {
+ try (Git local = new Git(db);
+ Git remote = new Git(createBareRepository())) {
+ connectLocalToRemote(local, remote);
+
+ final StoredConfig config2 = remote.getRepository().getConfig();
+ config2.setBoolean("receive", null, "pushoptions", true);
+ config2.save();
+
+ RevCommit commit = addCommit(local);
+
+ local.checkout().setName("not-pushed").setCreateBranch(true).call();
+ local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+ assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+ PushCommand pushCommand = local.push().setRemote("test")
+ .setPushOptions(null);
+ pushCommand.call();
+
+ assertEquals(commit.getId(),
+ remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+ }
+ }
+
+ @Test(expected = TransportException.class)
+ public void testPushOptionsNotSupported() throws Exception {
+ try (Git local = new Git(db);
+ Git remote = new Git(createBareRepository())) {
+ connectLocalToRemote(local, remote);
+
+ final StoredConfig config2 = remote.getRepository().getConfig();
+ config2.setBoolean("receive", null, "pushoptions", false);
+ config2.save();
+
+ addCommit(local);
+
+ local.checkout().setName("not-pushed").setCreateBranch(true).call();
+ local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+ assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+ assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+ assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+ List<String> pushOptions = new ArrayList<>();
+ PushCommand pushCommand = local.push().setRemote("test")
+ .setPushOptions(pushOptions);
+ pushCommand.call();
+
+ fail("should already have thrown TransportException");
+ }
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index e68bca0322..ebe1befee1 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -498,6 +498,7 @@ pushCertificateInvalidHeader=Push certificate has invalid header format
pushCertificateInvalidSignature=Push certificate has invalid signature format
pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
pushNotPermitted=push not permitted
+pushOptionsNotSupported=Push options not supported; received {0}
rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
readTimedOut=Read timed out after {0} ms
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index 0a49f78069..bd4521b517 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -96,6 +96,8 @@ public class PushCommand extends
private OutputStream out;
+ private List<String> pushOptions;
+
/**
* @param repo
*/
@@ -149,6 +151,7 @@ public class PushCommand extends
if (receivePack != null)
transport.setOptionReceivePack(receivePack);
transport.setDryRun(dryRun);
+ transport.setPushOptions(pushOptions);
configure(transport);
final Collection<RemoteRefUpdate> toPush = transport
@@ -189,7 +192,6 @@ public class PushCommand extends
}
return pushResults;
-
}
/**
@@ -453,4 +455,24 @@ public class PushCommand extends
this.out = out;
return this;
}
+
+ /**
+ * @return the option strings associated with the push operation
+ * @since 4.5
+ */
+ public List<String> getPushOptions() {
+ return pushOptions;
+ }
+
+ /**
+ * Sets the option strings associated with the push operation.
+ *
+ * @param pushOptions
+ * @return {@code this}
+ * @since 4.5
+ */
+ public PushCommand setPushOptions(List<String> pushOptions) {
+ this.pushOptions = pushOptions;
+ return this;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index b7ef0854c9..313512f990 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -557,6 +557,7 @@ public class JGitText extends TranslationBundle {
/***/ public String pushCertificateInvalidSignature;
/***/ public String pushIsNotSupportedForBundleTransport;
/***/ public String pushNotPermitted;
+ /***/ public String pushOptionsNotSupported;
/***/ public String rawLogMessageDoesNotParseAsLogEntry;
/***/ public String readingObjectsFromLocalRepositoryFailed;
/***/ public String readTimedOut;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index 266ca7b060..8550ec3a3f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -92,6 +92,9 @@ public class BatchRefUpdate {
/** Whether updates should be atomic. */
private boolean atomic;
+ /** Push options associated with this update. */
+ private List<String> pushOptions;
+
/**
* Initialize a new batch update.
*
@@ -301,27 +304,40 @@ public class BatchRefUpdate {
}
/**
+ * Gets the list of option strings associated with this update.
+ *
+ * @return pushOptions
+ * @since 4.5
+ */
+ public List<String> getPushOptions() {
+ return pushOptions;
+ }
+
+ /**
* Execute this batch update.
* <p>
* The default implementation of this method performs a sequential reference
* update over each reference.
* <p>
* Implementations must respect the atomicity requirements of the underlying
- * database as described in {@link #setAtomic(boolean)} and {@link
- * RefDatabase#performsAtomicTransactions()}.
+ * database as described in {@link #setAtomic(boolean)} and
+ * {@link RefDatabase#performsAtomicTransactions()}.
*
* @param walk
* a RevWalk to parse tags in case the storage system wants to
* store them pre-peeled, a common performance optimization.
* @param monitor
* progress monitor to receive update status on.
+ * @param options
+ * a list of option strings; set null to execute without
* @throws IOException
* the database is unable to accept the update. Individual
* command status must be tested to determine if there is a
* partial failure, or a total failure.
+ * @since 4.5
*/
- public void execute(RevWalk walk, ProgressMonitor monitor)
- throws IOException {
+ public void execute(RevWalk walk, ProgressMonitor monitor,
+ List<String> options) throws IOException {
if (atomic && !refdb.performsAtomicTransactions()) {
for (ReceiveCommand c : commands) {
@@ -333,6 +349,10 @@ public class BatchRefUpdate {
return;
}
+ if (options != null) {
+ pushOptions = options;
+ }
+
monitor.beginTask(JGitText.get().updatingReferences, commands.size());
List<ReceiveCommand> commands2 = new ArrayList<ReceiveCommand>(
commands.size());
@@ -412,6 +432,24 @@ public class BatchRefUpdate {
monitor.endTask();
}
+ /**
+ * Execute this batch update without option strings.
+ *
+ * @param walk
+ * a RevWalk to parse tags in case the storage system wants to
+ * store them pre-peeled, a common performance optimization.
+ * @param monitor
+ * progress monitor to receive update status on.
+ * @throws IOException
+ * the database is unable to accept the update. Individual
+ * command status must be tested to determine if there is a
+ * partial failure, or a total failure.
+ */
+ public void execute(RevWalk walk, ProgressMonitor monitor)
+ throws IOException {
+ execute(walk, monitor, null);
+ }
+
private static Collection<String> getTakenPrefixes(
final Collection<String> names) {
Collection<String> ref = new HashSet<String>();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 0cbbdc77e3..86cc484e34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -52,6 +52,7 @@ import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -113,14 +114,24 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
*/
public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
+ /**
+ * The server supports the receiving of push options.
+ * @since 4.5
+ */
+ public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
+
private final boolean thinPack;
private final boolean atomic;
+ /** A list of option strings associated with this push. */
+ private List<String> pushOptions;
+
private boolean capableAtomic;
private boolean capableDeleteRefs;
private boolean capableReport;
private boolean capableSideBand;
private boolean capableOfsDelta;
+ private boolean capablePushOptions;
private boolean sentCommand;
private boolean writePack;
@@ -138,6 +149,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
super(packTransport);
thinPack = transport.isPushThin();
atomic = transport.isPushAtomic();
+ pushOptions = transport.getPushOptions();
}
public void push(final ProgressMonitor monitor,
@@ -197,6 +209,9 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
OutputStream outputStream) throws TransportException {
try {
writeCommands(refUpdates.values(), monitor, outputStream);
+
+ if (pushOptions != null && capablePushOptions)
+ transmitOptions();
if (writePack)
writePack(refUpdates, monitor);
if (sentCommand) {
@@ -232,6 +247,12 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
JGitText.get().atomicPushNotSupported);
}
+ if (pushOptions != null && !capablePushOptions) {
+ throw new TransportException(uri,
+ MessageFormat.format(JGitText.get().pushOptionsNotSupported,
+ pushOptions.toString()));
+ }
+
for (final RemoteRefUpdate rru : refUpdates) {
if (!capableDeleteRefs && rru.isDelete()) {
rru.setStatus(Status.REJECTED_NODELETE);
@@ -269,6 +290,14 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
outNeedsEnd = false;
}
+ private void transmitOptions() throws IOException {
+ for (final String pushOption : pushOptions) {
+ pckOut.writeString(pushOption);
+ }
+
+ pckOut.end();
+ }
+
private String enableCapabilities(final ProgressMonitor monitor,
OutputStream outputStream) {
final StringBuilder line = new StringBuilder();
@@ -278,6 +307,10 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
+ if (pushOptions != null) {
+ capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
+ }
+
capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
if (capableSideBand) {
in = new SideBandInputStream(in, monitor, getMessageWriter(),
@@ -333,7 +366,8 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
throws IOException {
final String unpackLine = readStringLongTimeout();
if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$
- throw new PackProtocolException(uri, MessageFormat.format(JGitText.get().unexpectedReportLine, unpackLine));
+ throw new PackProtocolException(uri, MessageFormat
+ .format(JGitText.get().unexpectedReportLine, unpackLine));
final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$
if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$
throw new TooLargePackException(uri,
@@ -404,6 +438,16 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
}
}
+ /**
+ * Gets the list of option strings associated with this push.
+ *
+ * @return pushOptions
+ * @since 4.5
+ */
+ public List<String> getPushOptions() {
+ return pushOptions;
+ }
+
private static class CheckingSideBandOutputStream extends OutputStream {
private final InputStream in;
private final OutputStream out;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index aae4bd9c3c..b9923b95e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -48,6 +48,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
@@ -178,6 +179,9 @@ public abstract class BaseReceivePack {
/** Should an incoming transfer permit non-fast-forward requests? */
private boolean allowNonFastForwards;
+ /** Should an incoming transfer permit push options? **/
+ private boolean allowPushOptions;
+
/**
* Should the requested ref updates be performed as a single atomic
* transaction?
@@ -247,6 +251,18 @@ public abstract class BaseReceivePack {
private boolean quiet;
+ /**
+ * A list of option strings associated with a push.
+ * @since 4.5
+ */
+ protected List<String> pushOptions;
+
+ /**
+ * Whether the client intends to use push options.
+ * @since 4.5
+ */
+ protected boolean usePushOptions;
+
/** Lock around the received pack file, while updating refs. */
private PackLock packLock;
@@ -311,6 +327,7 @@ public abstract class BaseReceivePack {
allowBranchDeletes = rc.allowDeletes;
allowNonFastForwards = rc.allowNonFastForwards;
allowOfsDelta = rc.allowOfsDelta;
+ allowPushOptions = rc.allowPushOptions;
advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
refFilter = RefFilter.DEFAULT;
advertisedHaves = new HashSet<ObjectId>();
@@ -330,6 +347,8 @@ public abstract class BaseReceivePack {
final boolean allowDeletes;
final boolean allowNonFastForwards;
final boolean allowOfsDelta;
+ final boolean allowPushOptions;
+
final SignedPushConfig signedPush;
ReceiveConfig(final Config config) {
@@ -339,6 +358,8 @@ public abstract class BaseReceivePack {
"denynonfastforwards", false); //$NON-NLS-1$
allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
true);
+ allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$
+ false);
signedPush = SignedPushConfig.KEY.parse(config);
}
}
@@ -788,6 +809,25 @@ public abstract class BaseReceivePack {
}
/**
+ * @return true if the server supports the receiving of push options.
+ * @since 4.5
+ */
+ public boolean isAllowPushOptions() {
+ return allowPushOptions;
+ }
+
+ /**
+ * Configure if the server supports the receiving of push options.
+ *
+ * @param allow
+ * true to permit option strings.
+ * @since 4.5
+ */
+ public void setAllowPushOptions(boolean allow) {
+ allowPushOptions = allow;
+ }
+
+ /**
* True if the client wants less verbose output.
*
* @return true if the client has requested the server to be less verbose.
@@ -805,6 +845,24 @@ public abstract class BaseReceivePack {
}
/**
+ * Gets the list of string options associated with this push.
+ *
+ * @return pushOptions
+ * @throws RequestNotYetReadException
+ * if the client's request has not yet been read from the wire,
+ * so we do not know if they expect push options. Note that the
+ * client may have already written the request, it just has not
+ * been read.
+ * @since 4.5
+ */
+ public List<String> getPushOptions() throws RequestNotYetReadException {
+ if (enabledCapabilities == null) {
+ throw new RequestNotYetReadException();
+ }
+ return Collections.unmodifiableList(pushOptions);
+ }
+
+ /**
* Set the configuration for push certificate verification.
*
* @param cfg
@@ -1076,6 +1134,10 @@ public abstract class BaseReceivePack {
adv.advertiseCapability(CAPABILITY_ATOMIC);
if (allowOfsDelta)
adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+ if (allowPushOptions) {
+ adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
+ pushOptions = new ArrayList<>();
+ }
adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
adv.send(getAdvertisedOrDefaultRefs());
for (ObjectId obj : advertisedHaves)
@@ -1192,6 +1254,8 @@ public abstract class BaseReceivePack {
protected void enableCapabilities() {
sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET);
+ usePushOptions = allowPushOptions
+ && isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
if (sideBand) {
OutputStream out = rawOut;
@@ -1205,6 +1269,17 @@ public abstract class BaseReceivePack {
}
/**
+ * Sets the client's intention regarding push options.
+ *
+ * @param usePushOptions
+ * whether the client intends to use push options
+ * @since 4.5
+ */
+ public void setUsePushOptions(boolean usePushOptions) {
+ this.usePushOptions = usePushOptions;
+ }
+
+ /**
* Check if the peer requested a capability.
*
* @param name
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index efde062621..2031147820 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -208,6 +208,13 @@ public class GitProtocolConstants {
*/
public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$
+ /**
+ * The server supports the receiving of push options.
+ *
+ * @since 4.5
+ */
+ public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$
+
static enum MultiAck {
OFF, CONTINUE, DETAILED;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 5cea88215a..5590c2d256 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -49,6 +49,7 @@ import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -87,6 +88,9 @@ class PushProcess {
/** an outputstream to write messages to */
private final OutputStream out;
+ /** A list of option strings associated with this push */
+ private List<String> pushOptions;
+
/**
* Create process for specified transport and refs updates specification.
*
@@ -122,6 +126,7 @@ class PushProcess {
this.transport = transport;
this.toPush = new HashMap<String, RemoteRefUpdate>();
this.out = out;
+ this.pushOptions = transport.getPushOptions();
for (final RemoteRefUpdate rru : toPush) {
if (this.toPush.put(rru.getRemoteName(), rru) != null)
throw new TransportException(MessageFormat.format(
@@ -294,4 +299,14 @@ class PushProcess {
}
}
}
+
+ /**
+ * Gets the list of option strings associated with this push.
+ *
+ * @return pushOptions
+ * @since 4.5
+ */
+ public List<String> getPushOptions() {
+ return pushOptions;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 2477806bd9..d16b723ff8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -174,6 +174,15 @@ public class ReceivePack extends BaseReceivePack {
super.enableCapabilities();
}
+ private void readPushOptions() throws IOException {
+ String pushOption = pckIn.readString();
+
+ while (pushOption != PacketLineIn.END) {
+ pushOptions.add(pushOption);
+ pushOption = pckIn.readString();
+ }
+ }
+
private void service() throws IOException {
if (isBiDirectionalPipe()) {
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -184,6 +193,10 @@ public class ReceivePack extends BaseReceivePack {
return;
recvCommands();
if (hasCommands()) {
+ if (usePushOptions) {
+ readPushOptions();
+ }
+
Throwable unpackError = null;
if (needPack()) {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 862b3bdeb0..bc4843a8af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -773,6 +773,9 @@ public abstract class Transport implements AutoCloseable {
/** Assists with authentication the connection. */
private CredentialsProvider credentialsProvider;
+ /** The option strings associated with the push operation. */
+ private List<String> pushOptions;
+
private PrintStream hookOutRedirect;
private PrePushHook prePush;
@@ -1121,6 +1124,25 @@ public abstract class Transport implements AutoCloseable {
}
/**
+ * @return the option strings associated with the push operation
+ * @since 4.5
+ */
+ public List<String> getPushOptions() {
+ return pushOptions;
+ }
+
+ /**
+ * Sets the option strings associated with the push operation.
+ *
+ * @param pushOptions
+ * null if push options are unsupported
+ * @since 4.5
+ */
+ public void setPushOptions(final List<String> pushOptions) {
+ this.pushOptions = pushOptions;
+ }
+
+ /**
* Fetch objects and refs from the remote repository to the local one.
* <p>
* This is a utility function providing standard fetch behavior. Local