Browse Source

Push implementation of option strings

Example usage:
$ ./jgit push \
  --push-option "Reviewer=j.doe@example.org" \
  --push-option "<arbitrary string>" \
  origin HEAD:refs/for/master
Stefan Beller has also made an equivalent change to CGit:
http://thread.gmane.org/gmane.comp.version-control.git/299872

Change-Id: I6797e50681054dce3bd179e80b731aef5e200d77
Signed-off-by: Dan Wang <dwwang@google.com>
stable-4.5
Dan Wang 9 years ago
parent
commit
7f9fb80002
  1. 4
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
  2. 363
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
  3. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  4. 24
      org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
  5. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  6. 46
      org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
  7. 46
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
  8. 75
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
  9. 7
      org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
  10. 15
      org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
  11. 13
      org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
  12. 22
      org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java

4
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()) {

363
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");
}
}
}

1
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

24
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;
}
}

1
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;

46
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.
*
@ -300,6 +303,16 @@ public class BatchRefUpdate {
return this;
}
/**
* 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>
@ -307,21 +320,24 @@ public class BatchRefUpdate {
* 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>();

46
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;

75
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);
}
}
@ -787,6 +808,25 @@ public abstract class BaseReceivePack {
allowQuiet = allow;
}
/**
* @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.
*
@ -804,6 +844,24 @@ public abstract class BaseReceivePack {
return quiet;
}
/**
* 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.
*
@ -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;
@ -1204,6 +1268,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.
*

7
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;
}

15
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;
}
}

13
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 {

22
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;
@ -1120,6 +1123,25 @@ public abstract class Transport implements AutoCloseable {
return credentialsProvider;
}
/**
* @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>

Loading…
Cancel
Save