From 98a41bd4d0695d4f632f029b06f17baabb28da2b Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Tue, 28 Sep 2010 09:54:44 -0500 Subject: [PATCH] Add PushCommand API Change-Id: Iff144a51fdc9a1112a21492c390a873a2b293bc9 Signed-off-by: Chris Aniszczyk --- .../org/eclipse/jgit/api/PushCommandTest.java | 96 +++++ .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 3 +- .../src/org/eclipse/jgit/api/Git.java | 13 + .../src/org/eclipse/jgit/api/PushCommand.java | 349 ++++++++++++++++++ 5 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java new file mode 100644 index 000000000..d9a07866c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010, Chris Aniszczyk + * 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.api; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +public class PushCommandTest extends RepositoryTestCase { + + public void testPush() throws JGitInternalException, IOException, + GitAPIException, URISyntaxException { + + // create other repository + Repository db2 = createWorkRepository(); + + // setup the first repository + final Config config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(db2.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.update(config); + + Git git1 = new Git(db); + // create some refs via commits and tag + RevCommit commit = git1.commit().setMessage("initial commit").call(); + RevTag tag = git1.tag().setName("tag").call(); + + try { + db2.resolve(commit.getId().getName() + "^{commit}"); + fail("id shouldn't exist yet"); + } catch (MissingObjectException e) { + // we should get here + } + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec) + .call(); + + assertEquals(commit.getId(), + db2.resolve(commit.getId().getName() + "^{commit}")); + assertEquals(tag.getId(), db2.resolve(tag.getId().getName())); + } + +} diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index db7a952b2..53f993dce 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -151,6 +151,7 @@ exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command exceptionCaughtDuringExecutionOfFetchCommand=Exception caught during execution of fetch command exceptionCaughtDuringExecutionOfMergeCommand=Exception caught during execution of merge command. {0} +exceptionCaughtDuringExecutionOfPushCommand=Exception caught during execution of push command exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command exceptionOccuredDuringAddingOfOptionToALogCommand=Exception occured during adding of {0} as option to a Log command exceptionOccuredDuringReadingOfGIT_DIR=Exception occured during reading of $GIT_DIR/{0}. {1} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index dceefa2f4..244dadd2f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -211,6 +211,7 @@ public class JGitText extends TranslationBundle { /***/ public String exceptionCaughtDuringExecutionOfCommitCommand; /***/ public String exceptionCaughtDuringExecutionOfFetchCommand; /***/ public String exceptionCaughtDuringExecutionOfMergeCommand; + /***/ public String exceptionCaughtDuringExecutionOfPushCommand; /***/ public String exceptionCaughtDuringExecutionOfTagCommand; /***/ public String exceptionOccuredDuringAddingOfOptionToALogCommand; /***/ public String exceptionOccuredDuringReadingOfGIT_DIR; @@ -397,7 +398,7 @@ public class JGitText extends TranslationBundle { /***/ public String resolvingDeltas; /***/ public String resultLengthIncorrect; /***/ public String searchForReuse; - /***/ public String sequenceTooLargeForDiffAlgorithm; + /***/ public String sequenceTooLargeForDiffAlgorithm; /***/ public String serviceNotPermitted; /***/ public String shortCompressedStreamAt; /***/ public String shortReadOfBlock; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 95a882704..480178b9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -172,6 +172,19 @@ public class Git { return new FetchCommand(repo); } + /** + * Returns a command object to execute a {@code Push} command + * + * @see Git documentation about Push + * @return a {@link PushCommand} used to collect all optional parameters and + * to finally execute the {@code Push} command + */ + public PushCommand push() { + return new PushCommand(repo); + } + /** * @return the git repository this class is interacting with */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java new file mode 100644 index 000000000..fc03a5898 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2010, Chris Aniszczyk + * 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.api; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.Transport; + +/** + * A class used to execute a {@code Push} command. It has setters for all + * supported options and arguments of this command and a {@link #call()} method + * to finally execute the command. + * + * @see Git documentation about Push + */ +public class PushCommand extends GitCommand> { + + private String remote = Constants.DEFAULT_REMOTE_NAME; + + private List refSpecs; + + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + + private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; + + private boolean dryRun; + + private boolean force; + + private boolean thin = Transport.DEFAULT_PUSH_THIN; + + private int timeout; + + /** + * @param repo + */ + protected PushCommand(Repository repo) { + super(repo); + refSpecs = new ArrayList(3); + } + + /** + * Executes the {@code push} command with all the options and parameters + * collected by the setter methods of this class. Each instance of this + * class should only be used for one invocation of the command (means: one + * call to {@link #call()}) + * + * @return an iteration over {@link PushResult} objects + * @throws InvalidRemoteException + * when called with an invalid remote uri + * @throws JGitInternalException + * a low-level exception of JGit has occurred. The original + * exception can be retrieved by calling + * {@link Exception#getCause()}. + */ + public Iterable call() throws JGitInternalException, + InvalidRemoteException { + checkCallable(); + + ArrayList pushResults = new ArrayList(3); + + try { + if (force) { + final List orig = new ArrayList(refSpecs); + refSpecs.clear(); + for (final RefSpec spec : orig) + refSpecs.add(spec.setForceUpdate(true)); + } + + final List transports; + transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); + for (final Transport transport : transports) { + if (0 <= timeout) + transport.setTimeout(timeout); + transport.setPushThin(thin); + if (receivePack != null) + transport.setOptionReceivePack(receivePack); + transport.setDryRun(dryRun); + + final Collection toPush = transport + .findRemoteRefUpdatesFor(refSpecs); + + try { + PushResult result = transport.push(monitor, toPush); + pushResults.add(result); + + } catch (TransportException e) { + throw new JGitInternalException( + JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, + e); + } finally { + transport.close(); + } + } + + } catch (URISyntaxException e) { + throw new InvalidRemoteException(MessageFormat.format( + JGitText.get().invalidRemote, remote)); + } catch (NotSupportedException e) { + throw new JGitInternalException( + JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, + e); + } catch (IOException e) { + throw new JGitInternalException( + JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, + e); + } + + return pushResults; + + } + + /** + * The remote (uri or name) used for the push operation. If no remote is + * set, the default value of Constants.DEFAULT_REMOTE_NAME will + * be used. + * + * @see Constants#DEFAULT_REMOTE_NAME + * @param remote + * @return {@code this} + */ + public PushCommand setRemote(String remote) { + checkCallable(); + this.remote = remote; + return this; + } + + /** + * @return the remote used for the remote operation + */ + public String getRemote() { + return remote; + } + + /** + * The remote executable providing receive-pack service for pack transports. + * If no receive-pack is set, the default value of + * RemoteConfig.DEFAULT_RECEIVE_PACK will be used. + * + * @see RemoteConfig#DEFAULT_RECEIVE_PACK + * @param receivePack + * @return {@code this} + */ + public PushCommand setReceivePack(String receivePack) { + checkCallable(); + this.receivePack = receivePack; + return this; + } + + /** + * @return the receive-pack used for the remote operation + */ + public String getReceivePack() { + return receivePack; + } + + /** + * @param timeout + * the timeout used for the push operation + * @return {@code this} + */ + public PushCommand setTimeout(int timeout) { + checkCallable(); + this.timeout = timeout; + return this; + } + + /** + * @return the timeout used for the push operation + */ + public int getTimeout() { + return timeout; + } + + /** + * @return the progress monitor for the push operation + */ + public ProgressMonitor getProgressMonitor() { + return monitor; + } + + /** + * The progress monitor associated with the push operation. By default, this + * is set to NullProgressMonitor + * + * @see NullProgressMonitor + * + * @param monitor + * @return {@code this} + */ + public PushCommand setProgressMonitor(ProgressMonitor monitor) { + checkCallable(); + this.monitor = monitor; + return this; + } + + /** + * @return the ref specs + */ + public List getRefSpecs() { + return refSpecs; + } + + /** + * The ref specs to be used in the push operation + * + * @param specs + * @return {@code this} + */ + public PushCommand setRefSpecs(RefSpec... specs) { + checkCallable(); + this.refSpecs.clear(); + Collections.addAll(refSpecs, specs); + return this; + } + + /** + * The ref specs to be used in the push operation + * + * @param specs + * @return {@code this} + */ + public PushCommand setRefSpecs(List specs) { + checkCallable(); + this.refSpecs.clear(); + this.refSpecs.addAll(specs); + return this; + } + + /** + * @return the dry run preference for the push operation + */ + public boolean isDryRun() { + return dryRun; + } + + /** + * Sets whether the push operation should be a dry run + * + * @param dryRun + * @return {@code this} + */ + public PushCommand setDryRun(boolean dryRun) { + checkCallable(); + this.dryRun = dryRun; + return this; + } + + /** + * @return the thin-pack preference for push operation + */ + public boolean isThin() { + return thin; + } + + /** + * Sets the thin-pack preference for push operation. + * + * Default setting is Transport.DEFAULT_PUSH_THIN + * + * @param thin + * @return {@code this} + */ + public PushCommand setThin(boolean thin) { + checkCallable(); + this.thin = thin; + return this; + } + + /** + * @return the force preference for push operation + */ + public boolean isForce() { + return force; + } + + /** + * Sets the force preference for push operation. + * + * @param force + * @return {@code this} + */ + public PushCommand setForce(boolean force) { + checkCallable(); + this.force = force; + return this; + } + +}