View Javadoc
1   /*
2    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.api;
44  
45  import java.io.IOException;
46  import java.io.OutputStream;
47  import java.net.URISyntaxException;
48  import java.text.MessageFormat;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.List;
53  
54  import org.eclipse.jgit.api.errors.GitAPIException;
55  import org.eclipse.jgit.api.errors.InvalidRemoteException;
56  import org.eclipse.jgit.api.errors.JGitInternalException;
57  import org.eclipse.jgit.errors.NotSupportedException;
58  import org.eclipse.jgit.errors.TooLargeObjectInPackException;
59  import org.eclipse.jgit.errors.TooLargePackException;
60  import org.eclipse.jgit.errors.TransportException;
61  import org.eclipse.jgit.internal.JGitText;
62  import org.eclipse.jgit.lib.Constants;
63  import org.eclipse.jgit.lib.NullProgressMonitor;
64  import org.eclipse.jgit.lib.ProgressMonitor;
65  import org.eclipse.jgit.lib.Ref;
66  import org.eclipse.jgit.lib.Repository;
67  import org.eclipse.jgit.transport.PushResult;
68  import org.eclipse.jgit.transport.RefSpec;
69  import org.eclipse.jgit.transport.RemoteConfig;
70  import org.eclipse.jgit.transport.RemoteRefUpdate;
71  import org.eclipse.jgit.transport.Transport;
72  
73  /**
74   * A class used to execute a {@code Push} command. It has setters for all
75   * supported options and arguments of this command and a {@link #call()} method
76   * to finally execute the command.
77   *
78   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-push.html"
79   *      >Git documentation about Push</a>
80   */
81  public class PushCommand extends
82  		TransportCommand<PushCommand, Iterable<PushResult>> {
83  
84  	private String remote = Constants.DEFAULT_REMOTE_NAME;
85  
86  	private final List<RefSpec> refSpecs;
87  
88  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
89  
90  	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
91  
92  	private boolean dryRun;
93  	private boolean atomic;
94  	private boolean force;
95  	private boolean thin = Transport.DEFAULT_PUSH_THIN;
96  
97  	private OutputStream out;
98  
99  	private List<String> pushOptions;
100 
101 	/**
102 	 * @param repo
103 	 */
104 	protected PushCommand(Repository repo) {
105 		super(repo);
106 		refSpecs = new ArrayList<RefSpec>(3);
107 	}
108 
109 	/**
110 	 * Executes the {@code push} command with all the options and parameters
111 	 * collected by the setter methods of this class. Each instance of this
112 	 * class should only be used for one invocation of the command (means: one
113 	 * call to {@link #call()})
114 	 *
115 	 * @return an iteration over {@link PushResult} objects
116 	 * @throws InvalidRemoteException
117 	 *             when called with an invalid remote uri
118 	 * @throws org.eclipse.jgit.api.errors.TransportException
119 	 *             when an error occurs with the transport
120 	 * @throws GitAPIException
121 	 */
122 	public Iterable<PushResult> call() throws GitAPIException,
123 			InvalidRemoteException,
124 			org.eclipse.jgit.api.errors.TransportException {
125 		checkCallable();
126 
127 		ArrayList<PushResult> pushResults = new ArrayList<PushResult>(3);
128 
129 		try {
130 			if (refSpecs.isEmpty()) {
131 				RemoteConfig config = new RemoteConfig(repo.getConfig(),
132 						getRemote());
133 				refSpecs.addAll(config.getPushRefSpecs());
134 			}
135 			if (refSpecs.isEmpty()) {
136 				Ref head = repo.exactRef(Constants.HEAD);
137 				if (head != null && head.isSymbolic())
138 					refSpecs.add(new RefSpec(head.getLeaf().getName()));
139 			}
140 
141 			if (force) {
142 				for (int i = 0; i < refSpecs.size(); i++)
143 					refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
144 			}
145 
146 			final List<Transport> transports;
147 			transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
148 			for (final Transport transport : transports) {
149 				transport.setPushThin(thin);
150 				transport.setPushAtomic(atomic);
151 				if (receivePack != null)
152 					transport.setOptionReceivePack(receivePack);
153 				transport.setDryRun(dryRun);
154 				transport.setPushOptions(pushOptions);
155 				configure(transport);
156 
157 				final Collection<RemoteRefUpdate> toPush = transport
158 						.findRemoteRefUpdatesFor(refSpecs);
159 
160 				try {
161 					PushResult result = transport.push(monitor, toPush, out);
162 					pushResults.add(result);
163 
164 				} catch (TooLargePackException e) {
165 					throw new org.eclipse.jgit.api.errors.TooLargePackException(
166 							e.getMessage(), e);
167 				} catch (TooLargeObjectInPackException e) {
168 					throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException(
169 							e.getMessage(), e);
170 				} catch (TransportException e) {
171 					throw new org.eclipse.jgit.api.errors.TransportException(
172 							e.getMessage(), e);
173 				} finally {
174 					transport.close();
175 				}
176 			}
177 
178 		} catch (URISyntaxException e) {
179 			throw new InvalidRemoteException(MessageFormat.format(
180 					JGitText.get().invalidRemote, remote));
181 		} catch (TransportException e) {
182 			throw new org.eclipse.jgit.api.errors.TransportException(
183 					e.getMessage(), e);
184 		} catch (NotSupportedException e) {
185 			throw new JGitInternalException(
186 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
187 					e);
188 		} catch (IOException e) {
189 			throw new JGitInternalException(
190 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
191 					e);
192 		}
193 
194 		return pushResults;
195 	}
196 
197 	/**
198 	 * The remote (uri or name) used for the push operation. If no remote is
199 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
200 	 * be used.
201 	 *
202 	 * @see Constants#DEFAULT_REMOTE_NAME
203 	 * @param remote
204 	 * @return {@code this}
205 	 */
206 	public PushCommand setRemote(String remote) {
207 		checkCallable();
208 		this.remote = remote;
209 		return this;
210 	}
211 
212 	/**
213 	 * @return the remote used for the remote operation
214 	 */
215 	public String getRemote() {
216 		return remote;
217 	}
218 
219 	/**
220 	 * The remote executable providing receive-pack service for pack transports.
221 	 * If no receive-pack is set, the default value of
222 	 * <code>RemoteConfig.DEFAULT_RECEIVE_PACK</code> will be used.
223 	 *
224 	 * @see RemoteConfig#DEFAULT_RECEIVE_PACK
225 	 * @param receivePack
226 	 * @return {@code this}
227 	 */
228 	public PushCommand setReceivePack(String receivePack) {
229 		checkCallable();
230 		this.receivePack = receivePack;
231 		return this;
232 	}
233 
234 	/**
235 	 * @return the receive-pack used for the remote operation
236 	 */
237 	public String getReceivePack() {
238 		return receivePack;
239 	}
240 
241 	/**
242 	 * @return the timeout used for the push operation
243 	 */
244 	public int getTimeout() {
245 		return timeout;
246 	}
247 
248 	/**
249 	 * @return the progress monitor for the push operation
250 	 */
251 	public ProgressMonitor getProgressMonitor() {
252 		return monitor;
253 	}
254 
255 	/**
256 	 * The progress monitor associated with the push operation. By default, this
257 	 * is set to <code>NullProgressMonitor</code>
258 	 *
259 	 * @see NullProgressMonitor
260 	 *
261 	 * @param monitor
262 	 * @return {@code this}
263 	 */
264 	public PushCommand setProgressMonitor(ProgressMonitor monitor) {
265 		checkCallable();
266 		if (monitor == null) {
267 			monitor = NullProgressMonitor.INSTANCE;
268 		}
269 		this.monitor = monitor;
270 		return this;
271 	}
272 
273 	/**
274 	 * @return the ref specs
275 	 */
276 	public List<RefSpec> getRefSpecs() {
277 		return refSpecs;
278 	}
279 
280 	/**
281 	 * The ref specs to be used in the push operation
282 	 *
283 	 * @param specs
284 	 * @return {@code this}
285 	 */
286 	public PushCommand setRefSpecs(RefSpec... specs) {
287 		checkCallable();
288 		this.refSpecs.clear();
289 		Collections.addAll(refSpecs, specs);
290 		return this;
291 	}
292 
293 	/**
294 	 * The ref specs to be used in the push operation
295 	 *
296 	 * @param specs
297 	 * @return {@code this}
298 	 */
299 	public PushCommand setRefSpecs(List<RefSpec> specs) {
300 		checkCallable();
301 		this.refSpecs.clear();
302 		this.refSpecs.addAll(specs);
303 		return this;
304 	}
305 
306 	/**
307 	 * Push all branches under refs/heads/*.
308 	 *
309 	 * @return {code this}
310 	 */
311 	public PushCommand setPushAll() {
312 		refSpecs.add(Transport.REFSPEC_PUSH_ALL);
313 		return this;
314 	}
315 
316 	/**
317 	 * Push all tags under refs/tags/*.
318 	 *
319 	 * @return {code this}
320 	 */
321 	public PushCommand setPushTags() {
322 		refSpecs.add(Transport.REFSPEC_TAGS);
323 		return this;
324 	}
325 
326 	/**
327 	 * Add a reference to push.
328 	 *
329 	 * @param ref
330 	 *            the source reference. The remote name will match.
331 	 * @return {@code this}.
332 	 */
333 	public PushCommand add(Ref ref) {
334 		refSpecs.add(new RefSpec(ref.getLeaf().getName()));
335 		return this;
336 	}
337 
338 	/**
339 	 * Add a reference to push.
340 	 *
341 	 * @param nameOrSpec
342 	 *            any reference name, or a reference specification.
343 	 * @return {@code this}.
344 	 * @throws JGitInternalException
345 	 *             the reference name cannot be resolved.
346 	 */
347 	public PushCommand add(String nameOrSpec) {
348 		if (0 <= nameOrSpec.indexOf(':')) {
349 			refSpecs.add(new RefSpec(nameOrSpec));
350 		} else {
351 			Ref src;
352 			try {
353 				src = repo.findRef(nameOrSpec);
354 			} catch (IOException e) {
355 				throw new JGitInternalException(
356 						JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
357 						e);
358 			}
359 			if (src != null)
360 				add(src);
361 		}
362 		return this;
363 	}
364 
365 	/**
366 	 * @return the dry run preference for the push operation
367 	 */
368 	public boolean isDryRun() {
369 		return dryRun;
370 	}
371 
372 	/**
373 	 * Sets whether the push operation should be a dry run
374 	 *
375 	 * @param dryRun
376 	 * @return {@code this}
377 	 */
378 	public PushCommand setDryRun(boolean dryRun) {
379 		checkCallable();
380 		this.dryRun = dryRun;
381 		return this;
382 	}
383 
384 	/**
385 	 * @return the thin-pack preference for push operation
386 	 */
387 	public boolean isThin() {
388 		return thin;
389 	}
390 
391 	/**
392 	 * Sets the thin-pack preference for push operation.
393 	 *
394 	 * Default setting is Transport.DEFAULT_PUSH_THIN
395 	 *
396 	 * @param thin
397 	 * @return {@code this}
398 	 */
399 	public PushCommand setThin(boolean thin) {
400 		checkCallable();
401 		this.thin = thin;
402 		return this;
403 	}
404 
405 	/**
406 	 * @return true if all-or-nothing behavior is requested.
407 	 * @since 4.2
408 	 */
409 	public boolean isAtomic() {
410 		return atomic;
411 	}
412 
413 	/**
414 	 * Requests atomic push (all references updated, or no updates).
415 	 *
416 	 * Default setting is false.
417 	 *
418 	 * @param atomic
419 	 * @return {@code this}
420 	 * @since 4.2
421 	 */
422 	public PushCommand setAtomic(boolean atomic) {
423 		checkCallable();
424 		this.atomic = atomic;
425 		return this;
426 	}
427 
428 	/**
429 	 * @return the force preference for push operation
430 	 */
431 	public boolean isForce() {
432 		return force;
433 	}
434 
435 	/**
436 	 * Sets the force preference for push operation.
437 	 *
438 	 * @param force
439 	 * @return {@code this}
440 	 */
441 	public PushCommand setForce(boolean force) {
442 		checkCallable();
443 		this.force = force;
444 		return this;
445 	}
446 
447 	/**
448 	 * Sets the output stream to write sideband messages to
449 	 *
450 	 * @param out
451 	 * @return {@code this}
452 	 * @since 3.0
453 	 */
454 	public PushCommand setOutputStream(OutputStream out) {
455 		this.out = out;
456 		return this;
457 	}
458 
459 	/**
460 	 * @return the option strings associated with the push operation
461 	 * @since 4.5
462 	 */
463 	public List<String> getPushOptions() {
464 		return pushOptions;
465 	}
466 
467 	/**
468 	 * Sets the option strings associated with the push operation.
469 	 *
470 	 * @param pushOptions
471 	 * @return {@code this}
472 	 * @since 4.5
473 	 */
474 	public PushCommand setPushOptions(List<String> pushOptions) {
475 		this.pushOptions = pushOptions;
476 		return this;
477 	}
478 }