View Javadoc
1   /*
2    * Copyright (C) 2010, 2022 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.DetachedHeadException;
25  import org.eclipse.jgit.api.errors.GitAPIException;
26  import org.eclipse.jgit.api.errors.InvalidRefNameException;
27  import org.eclipse.jgit.api.errors.InvalidRemoteException;
28  import org.eclipse.jgit.api.errors.JGitInternalException;
29  import org.eclipse.jgit.errors.NotSupportedException;
30  import org.eclipse.jgit.errors.TooLargeObjectInPackException;
31  import org.eclipse.jgit.errors.TooLargePackException;
32  import org.eclipse.jgit.errors.TransportException;
33  import org.eclipse.jgit.internal.JGitText;
34  import org.eclipse.jgit.lib.BranchConfig;
35  import org.eclipse.jgit.lib.Config;
36  import org.eclipse.jgit.lib.ConfigConstants;
37  import org.eclipse.jgit.lib.Constants;
38  import org.eclipse.jgit.lib.NullProgressMonitor;
39  import org.eclipse.jgit.lib.ProgressMonitor;
40  import org.eclipse.jgit.lib.Ref;
41  import org.eclipse.jgit.lib.Repository;
42  import org.eclipse.jgit.transport.PushConfig;
43  import org.eclipse.jgit.transport.PushConfig.PushDefault;
44  import org.eclipse.jgit.transport.PushResult;
45  import org.eclipse.jgit.transport.RefLeaseSpec;
46  import org.eclipse.jgit.transport.RefSpec;
47  import org.eclipse.jgit.transport.RemoteConfig;
48  import org.eclipse.jgit.transport.RemoteRefUpdate;
49  import org.eclipse.jgit.transport.Transport;
50  
51  /**
52   * A class used to execute a {@code Push} command. It has setters for all
53   * supported options and arguments of this command and a {@link #call()} method
54   * to finally execute the command.
55   *
56   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-push.html"
57   *      >Git documentation about Push</a>
58   */
59  public class PushCommand extends
60  		TransportCommand<PushCommand, Iterable<PushResult>> {
61  
62  	private String remote;
63  
64  	private final List<RefSpec> refSpecs;
65  
66  	private final Map<String, RefLeaseSpec> refLeaseSpecs;
67  
68  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
69  
70  	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
71  
72  	private boolean dryRun;
73  	private boolean atomic;
74  	private boolean force;
75  	private boolean thin = Transport.DEFAULT_PUSH_THIN;
76  
77  	private OutputStream out;
78  
79  	private List<String> pushOptions;
80  
81  	// Legacy behavior as default. Use setPushDefault(null) to determine the
82  	// value from the git config.
83  	private PushDefault pushDefault = PushDefault.CURRENT;
84  
85  	/**
86  	 * <p>
87  	 * Constructor for PushCommand.
88  	 * </p>
89  	 *
90  	 * @param repo
91  	 *            the {@link org.eclipse.jgit.lib.Repository}
92  	 */
93  	protected PushCommand(Repository repo) {
94  		super(repo);
95  		refSpecs = new ArrayList<>(3);
96  		refLeaseSpecs = new HashMap<>();
97  	}
98  
99  	/**
100 	 * {@inheritDoc}
101 	 * <p>
102 	 * Execute the {@code push} command with all the options and parameters
103 	 * collected by the setter methods of this class. Each instance of this
104 	 * class should only be used for one invocation of the command (means: one
105 	 * call to {@link #call()})
106 	 */
107 	@Override
108 	public Iterable<PushResult> call() throws GitAPIException,
109 			InvalidRemoteException,
110 			org.eclipse.jgit.api.errors.TransportException {
111 		checkCallable();
112 		setCallable(false);
113 
114 		ArrayList<PushResult> pushResults = new ArrayList<>(3);
115 
116 		try {
117 			Config config = repo.getConfig();
118 			remote = determineRemote(config, remote);
119 			if (refSpecs.isEmpty()) {
120 				RemoteConfig rc = new RemoteConfig(config,
121 						getRemote());
122 				refSpecs.addAll(rc.getPushRefSpecs());
123 				if (refSpecs.isEmpty()) {
124 					determineDefaultRefSpecs(config);
125 				}
126 			}
127 
128 			if (force) {
129 				for (int i = 0; i < refSpecs.size(); i++)
130 					refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
131 			}
132 
133 			List<Transport> transports = Transport.openAll(repo, remote,
134 					Transport.Operation.PUSH);
135 			for (@SuppressWarnings("resource") // Explicitly closed in finally
136 					final Transport transport : transports) {
137 				transport.setPushThin(thin);
138 				transport.setPushAtomic(atomic);
139 				if (receivePack != null)
140 					transport.setOptionReceivePack(receivePack);
141 				transport.setDryRun(dryRun);
142 				transport.setPushOptions(pushOptions);
143 				configure(transport);
144 
145 				final Collection<RemoteRefUpdate> toPush = transport
146 						.findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs);
147 
148 				try {
149 					PushResult result = transport.push(monitor, toPush, out);
150 					pushResults.add(result);
151 
152 				} catch (TooLargePackException e) {
153 					throw new org.eclipse.jgit.api.errors.TooLargePackException(
154 							e.getMessage(), e);
155 				} catch (TooLargeObjectInPackException e) {
156 					throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException(
157 							e.getMessage(), e);
158 				} catch (TransportException e) {
159 					throw new org.eclipse.jgit.api.errors.TransportException(
160 							e.getMessage(), e);
161 				} finally {
162 					transport.close();
163 				}
164 			}
165 
166 		} catch (URISyntaxException e) {
167 			throw new InvalidRemoteException(
168 					MessageFormat.format(JGitText.get().invalidRemote, remote),
169 					e);
170 		} catch (TransportException e) {
171 			throw new org.eclipse.jgit.api.errors.TransportException(
172 					e.getMessage(), e);
173 		} catch (NotSupportedException e) {
174 			throw new JGitInternalException(
175 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
176 					e);
177 		} catch (IOException e) {
178 			throw new JGitInternalException(
179 					JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
180 					e);
181 		}
182 
183 		return pushResults;
184 	}
185 
186 	private String determineRemote(Config config, String remoteName)
187 			throws IOException {
188 		if (remoteName != null) {
189 			return remoteName;
190 		}
191 		Ref head = repo.exactRef(Constants.HEAD);
192 		String effectiveRemote = null;
193 		BranchConfig branchCfg = null;
194 		if (head != null && head.isSymbolic()) {
195 			String currentBranch = head.getLeaf().getName();
196 			branchCfg = new BranchConfig(config,
197 					Repository.shortenRefName(currentBranch));
198 			effectiveRemote = branchCfg.getPushRemote();
199 		}
200 		if (effectiveRemote == null) {
201 			effectiveRemote = config.getString(
202 					ConfigConstants.CONFIG_REMOTE_SECTION, null,
203 					ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
204 			if (effectiveRemote == null && branchCfg != null) {
205 				effectiveRemote = branchCfg.getRemote();
206 			}
207 		}
208 		if (effectiveRemote == null) {
209 			effectiveRemote = Constants.DEFAULT_REMOTE_NAME;
210 		}
211 		return effectiveRemote;
212 	}
213 
214 	private String getCurrentBranch()
215 			throws IOException, DetachedHeadException {
216 		Ref head = repo.exactRef(Constants.HEAD);
217 		if (head != null && head.isSymbolic()) {
218 			return head.getLeaf().getName();
219 		}
220 		throw new DetachedHeadException();
221 	}
222 
223 	private void determineDefaultRefSpecs(Config config)
224 			throws IOException, GitAPIException {
225 		if (pushDefault == null) {
226 			pushDefault = config.get(PushConfig::new).getPushDefault();
227 		}
228 		switch (pushDefault) {
229 		case CURRENT:
230 			refSpecs.add(new RefSpec(getCurrentBranch()));
231 			break;
232 		case MATCHING:
233 			refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
234 			break;
235 		case NOTHING:
236 			throw new InvalidRefNameException(
237 					JGitText.get().pushDefaultNothing);
238 		case SIMPLE:
239 		case UPSTREAM:
240 			String currentBranch = getCurrentBranch();
241 			BranchConfig branchCfg = new BranchConfig(config,
242 					Repository.shortenRefName(currentBranch));
243 			String fetchRemote = branchCfg.getRemote();
244 			if (fetchRemote == null) {
245 				fetchRemote = Constants.DEFAULT_REMOTE_NAME;
246 			}
247 			boolean isTriangular = !fetchRemote.equals(remote);
248 			if (isTriangular) {
249 				if (PushDefault.UPSTREAM.equals(pushDefault)) {
250 					throw new InvalidRefNameException(MessageFormat.format(
251 							JGitText.get().pushDefaultTriangularUpstream,
252 							remote, fetchRemote));
253 				}
254 				// Strange, but consistent with C git: "simple" doesn't even
255 				// check whether there is a configured upstream, and if so, that
256 				// it is equal to the local branch name. It just becomes
257 				// "current".
258 				refSpecs.add(new RefSpec(currentBranch));
259 			} else {
260 				String trackedBranch = branchCfg.getMerge();
261 				if (branchCfg.isRemoteLocal() || trackedBranch == null
262 						|| !trackedBranch.startsWith(Constants.R_HEADS)) {
263 					throw new InvalidRefNameException(MessageFormat.format(
264 							JGitText.get().pushDefaultNoUpstream,
265 							currentBranch));
266 				}
267 				if (PushDefault.SIMPLE.equals(pushDefault)
268 						&& !trackedBranch.equals(currentBranch)) {
269 					throw new InvalidRefNameException(MessageFormat.format(
270 							JGitText.get().pushDefaultSimple, currentBranch,
271 							trackedBranch));
272 				}
273 				refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch));
274 			}
275 			break;
276 		default:
277 			throw new InvalidRefNameException(MessageFormat
278 					.format(JGitText.get().pushDefaultUnknown, pushDefault));
279 		}
280 	}
281 
282 	/**
283 	 * The remote (uri or name) used for the push operation. If no remote is
284 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
285 	 * be used.
286 	 *
287 	 * @see Constants#DEFAULT_REMOTE_NAME
288 	 * @param remote
289 	 *            the remote name
290 	 * @return {@code this}
291 	 */
292 	public PushCommand setRemote(String remote) {
293 		checkCallable();
294 		this.remote = remote;
295 		return this;
296 	}
297 
298 	/**
299 	 * Get remote name
300 	 *
301 	 * @return the remote used for the remote operation
302 	 */
303 	public String getRemote() {
304 		return remote;
305 	}
306 
307 	/**
308 	 * The remote executable providing receive-pack service for pack transports.
309 	 * If no receive-pack is set, the default value of
310 	 * <code>RemoteConfig.DEFAULT_RECEIVE_PACK</code> will be used.
311 	 *
312 	 * @see RemoteConfig#DEFAULT_RECEIVE_PACK
313 	 * @param receivePack
314 	 *            name of the remote executable providing the receive-pack
315 	 *            service
316 	 * @return {@code this}
317 	 */
318 	public PushCommand setReceivePack(String receivePack) {
319 		checkCallable();
320 		this.receivePack = receivePack;
321 		return this;
322 	}
323 
324 	/**
325 	 * Get the name of the remote executable providing the receive-pack service
326 	 *
327 	 * @return the receive-pack used for the remote operation
328 	 */
329 	public String getReceivePack() {
330 		return receivePack;
331 	}
332 
333 	/**
334 	 * Get timeout used for push operation
335 	 *
336 	 * @return the timeout used for the push operation
337 	 */
338 	public int getTimeout() {
339 		return timeout;
340 	}
341 
342 	/**
343 	 * Get the progress monitor
344 	 *
345 	 * @return the progress monitor for the push operation
346 	 */
347 	public ProgressMonitor getProgressMonitor() {
348 		return monitor;
349 	}
350 
351 	/**
352 	 * The progress monitor associated with the push operation. By default, this
353 	 * is set to <code>NullProgressMonitor</code>
354 	 *
355 	 * @see NullProgressMonitor
356 	 * @param monitor
357 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
358 	 * @return {@code this}
359 	 */
360 	public PushCommand setProgressMonitor(ProgressMonitor monitor) {
361 		checkCallable();
362 		if (monitor == null) {
363 			monitor = NullProgressMonitor.INSTANCE;
364 		}
365 		this.monitor = monitor;
366 		return this;
367 	}
368 
369 	/**
370 	 * Get the <code>RefLeaseSpec</code>s.
371 	 *
372 	 * @return the <code>RefLeaseSpec</code>s
373 	 * @since 4.7
374 	 */
375 	public List<RefLeaseSpec> getRefLeaseSpecs() {
376 		return new ArrayList<>(refLeaseSpecs.values());
377 	}
378 
379 	/**
380 	 * The ref lease specs to be used in the push operation, for a
381 	 * force-with-lease push operation.
382 	 *
383 	 * @param specs
384 	 *            a {@link org.eclipse.jgit.transport.RefLeaseSpec} object.
385 	 * @return {@code this}
386 	 * @since 4.7
387 	 */
388 	public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) {
389 		return setRefLeaseSpecs(Arrays.asList(specs));
390 	}
391 
392 	/**
393 	 * The ref lease specs to be used in the push operation, for a
394 	 * force-with-lease push operation.
395 	 *
396 	 * @param specs
397 	 *            list of {@code RefLeaseSpec}s
398 	 * @return {@code this}
399 	 * @since 4.7
400 	 */
401 	public PushCommand setRefLeaseSpecs(List<RefLeaseSpec> specs) {
402 		checkCallable();
403 		this.refLeaseSpecs.clear();
404 		for (RefLeaseSpec spec : specs) {
405 			refLeaseSpecs.put(spec.getRef(), spec);
406 		}
407 		return this;
408 	}
409 
410 	/**
411 	 * Get {@code RefSpec}s.
412 	 *
413 	 * @return the ref specs
414 	 */
415 	public List<RefSpec> getRefSpecs() {
416 		return refSpecs;
417 	}
418 
419 	/**
420 	 * The ref specs to be used in the push operation
421 	 *
422 	 * @param specs a {@link org.eclipse.jgit.transport.RefSpec} object.
423 	 * @return {@code this}
424 	 */
425 	public PushCommand setRefSpecs(RefSpec... specs) {
426 		checkCallable();
427 		this.refSpecs.clear();
428 		Collections.addAll(refSpecs, specs);
429 		return this;
430 	}
431 
432 	/**
433 	 * The ref specs to be used in the push operation
434 	 *
435 	 * @param specs
436 	 *            list of {@link org.eclipse.jgit.transport.RefSpec}s
437 	 * @return {@code this}
438 	 */
439 	public PushCommand setRefSpecs(List<RefSpec> specs) {
440 		checkCallable();
441 		this.refSpecs.clear();
442 		this.refSpecs.addAll(specs);
443 		return this;
444 	}
445 
446 	/**
447 	 * Retrieves the {@link PushDefault} currently set.
448 	 *
449 	 * @return the {@link PushDefault}, or {@code null} if not set
450 	 * @since 6.1
451 	 */
452 	public PushDefault getPushDefault() {
453 		return pushDefault;
454 	}
455 
456 	/**
457 	 * Sets an explicit {@link PushDefault}. The default used if this is not
458 	 * called is {@link PushDefault#CURRENT} for compatibility reasons with
459 	 * earlier JGit versions.
460 	 *
461 	 * @param pushDefault
462 	 *            {@link PushDefault} to set; if {@code null} the value defined
463 	 *            in the git config will be used.
464 	 *
465 	 * @return {@code this}
466 	 * @since 6.1
467 	 */
468 	public PushCommand setPushDefault(PushDefault pushDefault) {
469 		checkCallable();
470 		this.pushDefault = pushDefault;
471 		return this;
472 	}
473 
474 	/**
475 	 * Push all branches under refs/heads/*.
476 	 *
477 	 * @return {@code this}
478 	 */
479 	public PushCommand setPushAll() {
480 		refSpecs.add(Transport.REFSPEC_PUSH_ALL);
481 		return this;
482 	}
483 
484 	/**
485 	 * Push all tags under refs/tags/*.
486 	 *
487 	 * @return {@code this}
488 	 */
489 	public PushCommand setPushTags() {
490 		refSpecs.add(Transport.REFSPEC_TAGS);
491 		return this;
492 	}
493 
494 	/**
495 	 * Add a reference to push.
496 	 *
497 	 * @param ref
498 	 *            the source reference. The remote name will match.
499 	 * @return {@code this}.
500 	 */
501 	public PushCommand add(Ref ref) {
502 		refSpecs.add(new RefSpec(ref.getLeaf().getName()));
503 		return this;
504 	}
505 
506 	/**
507 	 * Add a reference to push.
508 	 *
509 	 * @param nameOrSpec
510 	 *            any reference name, or a reference specification.
511 	 * @return {@code this}.
512 	 * @throws JGitInternalException
513 	 *             the reference name cannot be resolved.
514 	 */
515 	public PushCommand add(String nameOrSpec) {
516 		if (0 <= nameOrSpec.indexOf(':')) {
517 			refSpecs.add(new RefSpec(nameOrSpec));
518 		} else {
519 			Ref src;
520 			try {
521 				src = repo.findRef(nameOrSpec);
522 			} catch (IOException e) {
523 				throw new JGitInternalException(
524 						JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
525 						e);
526 			}
527 			if (src != null)
528 				add(src);
529 		}
530 		return this;
531 	}
532 
533 	/**
534 	 * Whether to run the push operation as a dry run
535 	 *
536 	 * @return the dry run preference for the push operation
537 	 */
538 	public boolean isDryRun() {
539 		return dryRun;
540 	}
541 
542 	/**
543 	 * Sets whether the push operation should be a dry run
544 	 *
545 	 * @param dryRun a boolean.
546 	 * @return {@code this}
547 	 */
548 	public PushCommand setDryRun(boolean dryRun) {
549 		checkCallable();
550 		this.dryRun = dryRun;
551 		return this;
552 	}
553 
554 	/**
555 	 * Get the thin-pack preference
556 	 *
557 	 * @return the thin-pack preference for push operation
558 	 */
559 	public boolean isThin() {
560 		return thin;
561 	}
562 
563 	/**
564 	 * Set the thin-pack preference for push operation.
565 	 *
566 	 * Default setting is Transport.DEFAULT_PUSH_THIN
567 	 *
568 	 * @param thin
569 	 *            the thin-pack preference value
570 	 * @return {@code this}
571 	 */
572 	public PushCommand setThin(boolean thin) {
573 		checkCallable();
574 		this.thin = thin;
575 		return this;
576 	}
577 
578 	/**
579 	 * Whether this push should be executed atomically (all references updated,
580 	 * or none)
581 	 *
582 	 * @return true if all-or-nothing behavior is requested.
583 	 * @since 4.2
584 	 */
585 	public boolean isAtomic() {
586 		return atomic;
587 	}
588 
589 	/**
590 	 * Requests atomic push (all references updated, or no updates).
591 	 *
592 	 * Default setting is false.
593 	 *
594 	 * @param atomic
595 	 *            whether to run the push atomically
596 	 * @return {@code this}
597 	 * @since 4.2
598 	 */
599 	public PushCommand setAtomic(boolean atomic) {
600 		checkCallable();
601 		this.atomic = atomic;
602 		return this;
603 	}
604 
605 	/**
606 	 * Whether to push forcefully
607 	 *
608 	 * @return the force preference for push operation
609 	 */
610 	public boolean isForce() {
611 		return force;
612 	}
613 
614 	/**
615 	 * Sets the force preference for push operation.
616 	 *
617 	 * @param force
618 	 *            whether to push forcefully
619 	 * @return {@code this}
620 	 */
621 	public PushCommand setForce(boolean force) {
622 		checkCallable();
623 		this.force = force;
624 		return this;
625 	}
626 
627 	/**
628 	 * Sets the output stream to write sideband messages to
629 	 *
630 	 * @param out
631 	 *            an {@link java.io.OutputStream}
632 	 * @return {@code this}
633 	 * @since 3.0
634 	 */
635 	public PushCommand setOutputStream(OutputStream out) {
636 		this.out = out;
637 		return this;
638 	}
639 
640 	/**
641 	 * Get push options
642 	 *
643 	 * @return the option strings associated with the push operation
644 	 * @since 4.5
645 	 */
646 	public List<String> getPushOptions() {
647 		return pushOptions;
648 	}
649 
650 	/**
651 	 * Set the option strings associated with the push operation.
652 	 *
653 	 * @param pushOptions
654 	 *            a {@link java.util.List} of push option strings
655 	 * @return {@code this}
656 	 * @since 4.5
657 	 */
658 	public PushCommand setPushOptions(List<String> pushOptions) {
659 		this.pushOptions = pushOptions;
660 		return this;
661 	}
662 }