View Javadoc
1   /*
2    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import java.io.IOException;
13  import java.io.OutputStream;
14  import java.net.URISyntaxException;
15  import java.text.MessageFormat;
16  import java.util.ArrayList;
17  import java.util.Arrays;
18  import java.util.Collection;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.eclipse.jgit.api.errors.GitAPIException;
25  import org.eclipse.jgit.api.errors.InvalidRemoteException;
26  import org.eclipse.jgit.api.errors.JGitInternalException;
27  import org.eclipse.jgit.errors.NotSupportedException;
28  import org.eclipse.jgit.errors.TooLargeObjectInPackException;
29  import org.eclipse.jgit.errors.TooLargePackException;
30  import org.eclipse.jgit.errors.TransportException;
31  import org.eclipse.jgit.internal.JGitText;
32  import org.eclipse.jgit.lib.Constants;
33  import org.eclipse.jgit.lib.NullProgressMonitor;
34  import org.eclipse.jgit.lib.ProgressMonitor;
35  import org.eclipse.jgit.lib.Ref;
36  import org.eclipse.jgit.lib.Repository;
37  import org.eclipse.jgit.transport.PushResult;
38  import org.eclipse.jgit.transport.RefLeaseSpec;
39  import org.eclipse.jgit.transport.RefSpec;
40  import org.eclipse.jgit.transport.RemoteConfig;
41  import org.eclipse.jgit.transport.RemoteRefUpdate;
42  import org.eclipse.jgit.transport.Transport;
43  
44  /**
45   * A class used to execute a {@code Push} command. It has setters for all
46   * supported options and arguments of this command and a {@link #call()} method
47   * to finally execute the command.
48   *
49   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-push.html"
50   *      >Git documentation about Push</a>
51   */
52  public class PushCommand extends
53  		TransportCommand<PushCommand, Iterable<PushResult>> {
54  
55  	private String remote = Constants.DEFAULT_REMOTE_NAME;
56  
57  	private final List<RefSpec> refSpecs;
58  
59  	private final Map<String, RefLeaseSpec> refLeaseSpecs;
60  
61  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
62  
63  	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
64  
65  	private boolean dryRun;
66  	private boolean atomic;
67  	private boolean force;
68  	private boolean thin = Transport.DEFAULT_PUSH_THIN;
69  
70  	private OutputStream out;
71  
72  	private List<String> pushOptions;
73  
74  	/**
75  	 * <p>
76  	 * Constructor for PushCommand.
77  	 * </p>
78  	 *
79  	 * @param repo
80  	 *            the {@link org.eclipse.jgit.lib.Repository}
81  	 */
82  	protected PushCommand(Repository repo) {
83  		super(repo);
84  		refSpecs = new ArrayList<>(3);
85  		refLeaseSpecs = new HashMap<>();
86  	}
87  
88  	/**
89  	 * {@inheritDoc}
90  	 * <p>
91  	 * Execute the {@code push} command with all the options and parameters
92  	 * collected by the setter methods of this class. Each instance of this
93  	 * class should only be used for one invocation of the command (means: one
94  	 * call to {@link #call()})
95  	 */
96  	@Override
97  	public Iterable<PushResult> call() throws GitAPIException,
98  			InvalidRemoteException,
99  			org.eclipse.jgit.api.errors.TransportException {
100 		checkCallable();
101 
102 		ArrayList<PushResult> pushResults = new ArrayList<>(3);
103 
104 		try {
105 			if (refSpecs.isEmpty()) {
106 				RemoteConfig config = new RemoteConfig(repo.getConfig(),
107 						getRemote());
108 				refSpecs.addAll(config.getPushRefSpecs());
109 			}
110 			if (refSpecs.isEmpty()) {
111 				Ref head = repo.exactRef(Constants.HEAD);
112 				if (head != null && head.isSymbolic())
113 					refSpecs.add(new RefSpec(head.getLeaf().getName()));
114 			}
115 
116 			if (force) {
117 				for (int i = 0; i < refSpecs.size(); i++)
118 					refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
119 			}
120 
121 			final List<Transport> transports;
122 			transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
123 			for (@SuppressWarnings("resource") // Explicitly closed in finally
124 					final Transport transport : transports) {
125 				transport.setPushThin(thin);
126 				transport.setPushAtomic(atomic);
127 				if (receivePack != null)
128 					transport.setOptionReceivePack(receivePack);
129 				transport.setDryRun(dryRun);
130 				transport.setPushOptions(pushOptions);
131 				configure(transport);
132 
133 				final Collection<RemoteRefUpdate> toPush = transport
134 						.findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs);
135 
136 				try {
137 					PushResult result = transport.push(monitor, toPush, out);
138 					pushResults.add(result);
139 
140 				} catch (TooLargePackException e) {
141 					throw new org.eclipse.jgit.api.errors.TooLargePackException(
142 							e.getMessage(), e);
143 				} catch (TooLargeObjectInPackException e) {
144 					throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException(
145 							e.getMessage(), e);
146 				} catch (TransportException e) {
147 					throw new org.eclipse.jgit.api.errors.TransportException(
148 							e.getMessage(), e);
149 				} finally {
150 					transport.close();
151 				}
152 			}
153 
154 		} catch (URISyntaxException e) {
155 			throw new InvalidRemoteException(
156 					MessageFormat.format(JGitText.get().invalidRemote, remote),
157 					e);
158 		} catch (TransportException e) {
159 			throw new org.eclipse.jgit.api.errors.TransportException(
160 					e.getMessage(), e);
161 		} catch (NotSupportedException e) {
162 			throw new JGitInternalException(
163 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
164 					e);
165 		} catch (IOException e) {
166 			throw new JGitInternalException(
167 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
168 					e);
169 		}
170 
171 		return pushResults;
172 	}
173 
174 	/**
175 	 * The remote (uri or name) used for the push operation. If no remote is
176 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
177 	 * be used.
178 	 *
179 	 * @see Constants#DEFAULT_REMOTE_NAME
180 	 * @param remote
181 	 *            the remote name
182 	 * @return {@code this}
183 	 */
184 	public PushCommand setRemote(String remote) {
185 		checkCallable();
186 		this.remote = remote;
187 		return this;
188 	}
189 
190 	/**
191 	 * Get remote name
192 	 *
193 	 * @return the remote used for the remote operation
194 	 */
195 	public String getRemote() {
196 		return remote;
197 	}
198 
199 	/**
200 	 * The remote executable providing receive-pack service for pack transports.
201 	 * If no receive-pack is set, the default value of
202 	 * <code>RemoteConfig.DEFAULT_RECEIVE_PACK</code> will be used.
203 	 *
204 	 * @see RemoteConfig#DEFAULT_RECEIVE_PACK
205 	 * @param receivePack
206 	 *            name of the remote executable providing the receive-pack
207 	 *            service
208 	 * @return {@code this}
209 	 */
210 	public PushCommand setReceivePack(String receivePack) {
211 		checkCallable();
212 		this.receivePack = receivePack;
213 		return this;
214 	}
215 
216 	/**
217 	 * Get the name of the remote executable providing the receive-pack service
218 	 *
219 	 * @return the receive-pack used for the remote operation
220 	 */
221 	public String getReceivePack() {
222 		return receivePack;
223 	}
224 
225 	/**
226 	 * Get timeout used for push operation
227 	 *
228 	 * @return the timeout used for the push operation
229 	 */
230 	public int getTimeout() {
231 		return timeout;
232 	}
233 
234 	/**
235 	 * Get the progress monitor
236 	 *
237 	 * @return the progress monitor for the push operation
238 	 */
239 	public ProgressMonitor getProgressMonitor() {
240 		return monitor;
241 	}
242 
243 	/**
244 	 * The progress monitor associated with the push operation. By default, this
245 	 * is set to <code>NullProgressMonitor</code>
246 	 *
247 	 * @see NullProgressMonitor
248 	 * @param monitor
249 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
250 	 * @return {@code this}
251 	 */
252 	public PushCommand setProgressMonitor(ProgressMonitor monitor) {
253 		checkCallable();
254 		if (monitor == null) {
255 			monitor = NullProgressMonitor.INSTANCE;
256 		}
257 		this.monitor = monitor;
258 		return this;
259 	}
260 
261 	/**
262 	 * Get the <code>RefLeaseSpec</code>s.
263 	 *
264 	 * @return the <code>RefLeaseSpec</code>s
265 	 * @since 4.7
266 	 */
267 	public List<RefLeaseSpec> getRefLeaseSpecs() {
268 		return new ArrayList<>(refLeaseSpecs.values());
269 	}
270 
271 	/**
272 	 * The ref lease specs to be used in the push operation, for a
273 	 * force-with-lease push operation.
274 	 *
275 	 * @param specs
276 	 *            a {@link org.eclipse.jgit.transport.RefLeaseSpec} object.
277 	 * @return {@code this}
278 	 * @since 4.7
279 	 */
280 	public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) {
281 		return setRefLeaseSpecs(Arrays.asList(specs));
282 	}
283 
284 	/**
285 	 * The ref lease specs to be used in the push operation, for a
286 	 * force-with-lease push operation.
287 	 *
288 	 * @param specs
289 	 *            list of {@code RefLeaseSpec}s
290 	 * @return {@code this}
291 	 * @since 4.7
292 	 */
293 	public PushCommand setRefLeaseSpecs(List<RefLeaseSpec> specs) {
294 		checkCallable();
295 		this.refLeaseSpecs.clear();
296 		for (RefLeaseSpec spec : specs) {
297 			refLeaseSpecs.put(spec.getRef(), spec);
298 		}
299 		return this;
300 	}
301 
302 	/**
303 	 * Get {@code RefSpec}s.
304 	 *
305 	 * @return the ref specs
306 	 */
307 	public List<RefSpec> getRefSpecs() {
308 		return refSpecs;
309 	}
310 
311 	/**
312 	 * The ref specs to be used in the push operation
313 	 *
314 	 * @param specs a {@link org.eclipse.jgit.transport.RefSpec} object.
315 	 * @return {@code this}
316 	 */
317 	public PushCommand setRefSpecs(RefSpec... specs) {
318 		checkCallable();
319 		this.refSpecs.clear();
320 		Collections.addAll(refSpecs, specs);
321 		return this;
322 	}
323 
324 	/**
325 	 * The ref specs to be used in the push operation
326 	 *
327 	 * @param specs
328 	 *            list of {@link org.eclipse.jgit.transport.RefSpec}s
329 	 * @return {@code this}
330 	 */
331 	public PushCommand setRefSpecs(List<RefSpec> specs) {
332 		checkCallable();
333 		this.refSpecs.clear();
334 		this.refSpecs.addAll(specs);
335 		return this;
336 	}
337 
338 	/**
339 	 * Push all branches under refs/heads/*.
340 	 *
341 	 * @return {code this}
342 	 */
343 	public PushCommand setPushAll() {
344 		refSpecs.add(Transport.REFSPEC_PUSH_ALL);
345 		return this;
346 	}
347 
348 	/**
349 	 * Push all tags under refs/tags/*.
350 	 *
351 	 * @return {code this}
352 	 */
353 	public PushCommand setPushTags() {
354 		refSpecs.add(Transport.REFSPEC_TAGS);
355 		return this;
356 	}
357 
358 	/**
359 	 * Add a reference to push.
360 	 *
361 	 * @param ref
362 	 *            the source reference. The remote name will match.
363 	 * @return {@code this}.
364 	 */
365 	public PushCommand add(Ref ref) {
366 		refSpecs.add(new RefSpec(ref.getLeaf().getName()));
367 		return this;
368 	}
369 
370 	/**
371 	 * Add a reference to push.
372 	 *
373 	 * @param nameOrSpec
374 	 *            any reference name, or a reference specification.
375 	 * @return {@code this}.
376 	 * @throws JGitInternalException
377 	 *             the reference name cannot be resolved.
378 	 */
379 	public PushCommand add(String nameOrSpec) {
380 		if (0 <= nameOrSpec.indexOf(':')) {
381 			refSpecs.add(new RefSpec(nameOrSpec));
382 		} else {
383 			Ref src;
384 			try {
385 				src = repo.findRef(nameOrSpec);
386 			} catch (IOException e) {
387 				throw new JGitInternalException(
388 						JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
389 						e);
390 			}
391 			if (src != null)
392 				add(src);
393 		}
394 		return this;
395 	}
396 
397 	/**
398 	 * Whether to run the push operation as a dry run
399 	 *
400 	 * @return the dry run preference for the push operation
401 	 */
402 	public boolean isDryRun() {
403 		return dryRun;
404 	}
405 
406 	/**
407 	 * Sets whether the push operation should be a dry run
408 	 *
409 	 * @param dryRun a boolean.
410 	 * @return {@code this}
411 	 */
412 	public PushCommand setDryRun(boolean dryRun) {
413 		checkCallable();
414 		this.dryRun = dryRun;
415 		return this;
416 	}
417 
418 	/**
419 	 * Get the thin-pack preference
420 	 *
421 	 * @return the thin-pack preference for push operation
422 	 */
423 	public boolean isThin() {
424 		return thin;
425 	}
426 
427 	/**
428 	 * Set the thin-pack preference for push operation.
429 	 *
430 	 * Default setting is Transport.DEFAULT_PUSH_THIN
431 	 *
432 	 * @param thin
433 	 *            the thin-pack preference value
434 	 * @return {@code this}
435 	 */
436 	public PushCommand setThin(boolean thin) {
437 		checkCallable();
438 		this.thin = thin;
439 		return this;
440 	}
441 
442 	/**
443 	 * Whether this push should be executed atomically (all references updated,
444 	 * or none)
445 	 *
446 	 * @return true if all-or-nothing behavior is requested.
447 	 * @since 4.2
448 	 */
449 	public boolean isAtomic() {
450 		return atomic;
451 	}
452 
453 	/**
454 	 * Requests atomic push (all references updated, or no updates).
455 	 *
456 	 * Default setting is false.
457 	 *
458 	 * @param atomic
459 	 *            whether to run the push atomically
460 	 * @return {@code this}
461 	 * @since 4.2
462 	 */
463 	public PushCommand setAtomic(boolean atomic) {
464 		checkCallable();
465 		this.atomic = atomic;
466 		return this;
467 	}
468 
469 	/**
470 	 * Whether to push forcefully
471 	 *
472 	 * @return the force preference for push operation
473 	 */
474 	public boolean isForce() {
475 		return force;
476 	}
477 
478 	/**
479 	 * Sets the force preference for push operation.
480 	 *
481 	 * @param force
482 	 *            whether to push forcefully
483 	 * @return {@code this}
484 	 */
485 	public PushCommand setForce(boolean force) {
486 		checkCallable();
487 		this.force = force;
488 		return this;
489 	}
490 
491 	/**
492 	 * Sets the output stream to write sideband messages to
493 	 *
494 	 * @param out
495 	 *            an {@link java.io.OutputStream}
496 	 * @return {@code this}
497 	 * @since 3.0
498 	 */
499 	public PushCommand setOutputStream(OutputStream out) {
500 		this.out = out;
501 		return this;
502 	}
503 
504 	/**
505 	 * Get push options
506 	 *
507 	 * @return the option strings associated with the push operation
508 	 * @since 4.5
509 	 */
510 	public List<String> getPushOptions() {
511 		return pushOptions;
512 	}
513 
514 	/**
515 	 * Set the option strings associated with the push operation.
516 	 *
517 	 * @param pushOptions
518 	 *            a {@link java.util.List} of push option strings
519 	 * @return {@code this}
520 	 * @since 4.5
521 	 */
522 	public PushCommand setPushOptions(List<String> pushOptions) {
523 		this.pushOptions = pushOptions;
524 		return this;
525 	}
526 }