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 static java.util.stream.Collectors.toList;
46  
47  import java.io.IOException;
48  import java.net.URISyntaxException;
49  import java.text.MessageFormat;
50  import java.util.ArrayList;
51  import java.util.Arrays;
52  import java.util.List;
53  
54  import org.eclipse.jgit.annotations.Nullable;
55  import org.eclipse.jgit.api.errors.GitAPIException;
56  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
57  import org.eclipse.jgit.api.errors.InvalidRemoteException;
58  import org.eclipse.jgit.api.errors.JGitInternalException;
59  import org.eclipse.jgit.errors.ConfigInvalidException;
60  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
61  import org.eclipse.jgit.errors.NotSupportedException;
62  import org.eclipse.jgit.errors.TransportException;
63  import org.eclipse.jgit.internal.JGitText;
64  import org.eclipse.jgit.lib.ConfigConstants;
65  import org.eclipse.jgit.lib.Constants;
66  import org.eclipse.jgit.lib.NullProgressMonitor;
67  import org.eclipse.jgit.lib.ObjectId;
68  import org.eclipse.jgit.lib.ProgressMonitor;
69  import org.eclipse.jgit.lib.Repository;
70  import org.eclipse.jgit.lib.StoredConfig;
71  import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
72  import org.eclipse.jgit.revwalk.RevWalk;
73  import org.eclipse.jgit.submodule.SubmoduleWalk;
74  import org.eclipse.jgit.transport.FetchResult;
75  import org.eclipse.jgit.transport.RefSpec;
76  import org.eclipse.jgit.transport.TagOpt;
77  import org.eclipse.jgit.transport.Transport;
78  
79  /**
80   * A class used to execute a {@code Fetch} command. It has setters for all
81   * supported options and arguments of this command and a {@link #call()} method
82   * to finally execute the command.
83   *
84   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-fetch.html"
85   *      >Git documentation about Fetch</a>
86   */
87  public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
88  	private String remote = Constants.DEFAULT_REMOTE_NAME;
89  
90  	private List<RefSpec> refSpecs;
91  
92  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
93  
94  	private boolean checkFetchedObjects;
95  
96  	private Boolean removeDeletedRefs;
97  
98  	private boolean dryRun;
99  
100 	private boolean thin = Transport.DEFAULT_FETCH_THIN;
101 
102 	private TagOpt tagOption;
103 
104 	private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
105 
106 	private Callback callback;
107 
108 	private boolean isForceUpdate;
109 
110 	/**
111 	 * Callback for status of fetch operation.
112 	 *
113 	 * @since 4.8
114 	 *
115 	 */
116 	public interface Callback {
117 		/**
118 		 * Notify fetching a submodule.
119 		 *
120 		 * @param name
121 		 *            the submodule name.
122 		 */
123 		void fetchingSubmodule(String name);
124 	}
125 
126 	/**
127 	 * Constructor for FetchCommand.
128 	 *
129 	 * @param repo
130 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
131 	 */
132 	protected FetchCommand(Repository repo) {
133 		super(repo);
134 		refSpecs = new ArrayList<>(3);
135 	}
136 
137 	private FetchRecurseSubmodulesMode getRecurseMode(String path) {
138 		// Use the caller-specified mode, if set
139 		if (submoduleRecurseMode != null) {
140 			return submoduleRecurseMode;
141 		}
142 
143 		// Fall back to submodule.name.fetchRecurseSubmodules, if set
144 		FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(
145 				FetchRecurseSubmodulesMode.values(),
146 				ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
147 				ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null);
148 		if (mode != null) {
149 			return mode;
150 		}
151 
152 		// Fall back to fetch.recurseSubmodules, if set
153 		mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
154 				ConfigConstants.CONFIG_FETCH_SECTION, null,
155 				ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null);
156 		if (mode != null) {
157 			return mode;
158 		}
159 
160 		// Default to on-demand mode
161 		return FetchRecurseSubmodulesMode.ON_DEMAND;
162 	}
163 
164 	private void fetchSubmodules(FetchResult results)
165 			throws org.eclipse.jgit.api.errors.TransportException,
166 			GitAPIException, InvalidConfigurationException {
167 		try (SubmoduleWalk walk = new SubmoduleWalk(repo);
168 				RevWalk revWalk = new RevWalk(repo)) {
169 			// Walk over submodules in the parent repository's FETCH_HEAD.
170 			ObjectId fetchHead = repo.resolve(Constants.FETCH_HEAD);
171 			if (fetchHead == null) {
172 				return;
173 			}
174 			walk.setTree(revWalk.parseTree(fetchHead));
175 			while (walk.next()) {
176 				try (Repository submoduleRepo = walk.getRepository()) {
177 
178 					// Skip submodules that don't exist locally (have not been
179 					// cloned), are not registered in the .gitmodules file, or
180 					// not registered in the parent repository's config.
181 					if (submoduleRepo == null || walk.getModulesPath() == null
182 							|| walk.getConfigUrl() == null) {
183 						continue;
184 					}
185 
186 					FetchRecurseSubmodulesMode recurseMode = getRecurseMode(
187 							walk.getPath());
188 
189 					// When the fetch mode is "yes" we always fetch. When the
190 					// mode
191 					// is "on demand", we only fetch if the submodule's revision
192 					// was
193 					// updated to an object that is not currently present in the
194 					// submodule.
195 					if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND
196 							&& !submoduleRepo.hasObject(walk.getObjectId()))
197 							|| recurseMode == FetchRecurseSubmodulesMode.YES) {
198 						FetchCommand f = new FetchCommand(submoduleRepo)
199 								.setProgressMonitor(monitor)
200 								.setTagOpt(tagOption)
201 								.setCheckFetchedObjects(checkFetchedObjects)
202 								.setRemoveDeletedRefs(isRemoveDeletedRefs())
203 								.setThin(thin)
204 								.setRefSpecs(applyOptions(refSpecs))
205 								.setDryRun(dryRun)
206 								.setRecurseSubmodules(recurseMode);
207 						configure(f);
208 						if (callback != null) {
209 							callback.fetchingSubmodule(walk.getPath());
210 						}
211 						results.addSubmodule(walk.getPath(), f.call());
212 					}
213 				}
214 			}
215 		} catch (IOException e) {
216 			throw new JGitInternalException(e.getMessage(), e);
217 		} catch (ConfigInvalidException e) {
218 			throw new InvalidConfigurationException(e.getMessage(), e);
219 		}
220 	}
221 
222 	/**
223 	 * {@inheritDoc}
224 	 * <p>
225 	 * Execute the {@code fetch} command with all the options and parameters
226 	 * collected by the setter methods of this class. Each instance of this
227 	 * class should only be used for one invocation of the command (means: one
228 	 * call to {@link #call()})
229 	 */
230 	@Override
231 	public FetchResult call() throws GitAPIException, InvalidRemoteException,
232 			org.eclipse.jgit.api.errors.TransportException {
233 		checkCallable();
234 
235 		try (Transport transport = Transport.open(repo, remote)) {
236 			transport.setCheckFetchedObjects(checkFetchedObjects);
237 			transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
238 			transport.setDryRun(dryRun);
239 			if (tagOption != null)
240 				transport.setTagOpt(tagOption);
241 			transport.setFetchThin(thin);
242 			configure(transport);
243 			FetchResult result = transport.fetch(monitor,
244 					applyOptions(refSpecs));
245 			if (!repo.isBare()) {
246 				fetchSubmodules(result);
247 			}
248 
249 			return result;
250 		} catch (NoRemoteRepositoryException e) {
251 			throw new InvalidRemoteException(MessageFormat.format(
252 					JGitText.get().invalidRemote, remote), e);
253 		} catch (TransportException e) {
254 			throw new org.eclipse.jgit.api.errors.TransportException(
255 					e.getMessage(), e);
256 		} catch (URISyntaxException e) {
257 			throw new InvalidRemoteException(MessageFormat.format(
258 					JGitText.get().invalidRemote, remote));
259 		} catch (NotSupportedException e) {
260 			throw new JGitInternalException(
261 					JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
262 					e);
263 		}
264 
265 	}
266 
267 	private List<RefSpec> applyOptions(List<RefSpec> refSpecs2) {
268 		if (!isForceUpdate()) {
269 			return refSpecs2;
270 		}
271 		List<RefSpec> updated = new ArrayList<>(3);
272 		for (RefSpec refSpec : refSpecs2) {
273 			updated.add(refSpec.setForceUpdate(true));
274 		}
275 		return updated;
276 	}
277 
278 	/**
279 	 * Set the mode to be used for recursing into submodules.
280 	 *
281 	 * @param recurse
282 	 *            corresponds to the
283 	 *            --recurse-submodules/--no-recurse-submodules options. If
284 	 *            {@code null} use the value of the
285 	 *            {@code submodule.name.fetchRecurseSubmodules} option
286 	 *            configured per submodule. If not specified there, use the
287 	 *            value of the {@code fetch.recurseSubmodules} option configured
288 	 *            in git config. If not configured in either, "on-demand" is the
289 	 *            built-in default.
290 	 * @return {@code this}
291 	 * @since 4.7
292 	 */
293 	public FetchCommand setRecurseSubmodules(
294 			@Nullable FetchRecurseSubmodulesMode recurse) {
295 		checkCallable();
296 		submoduleRecurseMode = recurse;
297 		return this;
298 	}
299 
300 	/**
301 	 * The remote (uri or name) used for the fetch operation. If no remote is
302 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
303 	 * be used.
304 	 *
305 	 * @see Constants#DEFAULT_REMOTE_NAME
306 	 * @param remote
307 	 *            name of a remote
308 	 * @return {@code this}
309 	 */
310 	public FetchCommand setRemote(String remote) {
311 		checkCallable();
312 		this.remote = remote;
313 		return this;
314 	}
315 
316 	/**
317 	 * Get the remote
318 	 *
319 	 * @return the remote used for the remote operation
320 	 */
321 	public String getRemote() {
322 		return remote;
323 	}
324 
325 	/**
326 	 * Get timeout
327 	 *
328 	 * @return the timeout used for the fetch operation
329 	 */
330 	public int getTimeout() {
331 		return timeout;
332 	}
333 
334 	/**
335 	 * Whether to check received objects for validity
336 	 *
337 	 * @return whether to check received objects for validity
338 	 */
339 	public boolean isCheckFetchedObjects() {
340 		return checkFetchedObjects;
341 	}
342 
343 	/**
344 	 * If set to {@code true}, objects received will be checked for validity
345 	 *
346 	 * @param checkFetchedObjects
347 	 *            whether to check objects for validity
348 	 * @return {@code this}
349 	 */
350 	public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) {
351 		checkCallable();
352 		this.checkFetchedObjects = checkFetchedObjects;
353 		return this;
354 	}
355 
356 	/**
357 	 * Whether to remove refs which no longer exist in the source
358 	 *
359 	 * @return whether to remove refs which no longer exist in the source
360 	 */
361 	public boolean isRemoveDeletedRefs() {
362 		if (removeDeletedRefs != null)
363 			return removeDeletedRefs.booleanValue();
364 		else { // fall back to configuration
365 			boolean result = false;
366 			StoredConfig config = repo.getConfig();
367 			result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION,
368 					null, ConfigConstants.CONFIG_KEY_PRUNE, result);
369 			result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
370 					remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
371 			return result;
372 		}
373 	}
374 
375 	/**
376 	 * If set to {@code true}, refs are removed which no longer exist in the
377 	 * source
378 	 *
379 	 * @param removeDeletedRefs
380 	 *            whether to remove deleted {@code Ref}s
381 	 * @return {@code this}
382 	 */
383 	public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) {
384 		checkCallable();
385 		this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs);
386 		return this;
387 	}
388 
389 	/**
390 	 * Get progress monitor
391 	 *
392 	 * @return the progress monitor for the fetch operation
393 	 */
394 	public ProgressMonitor getProgressMonitor() {
395 		return monitor;
396 	}
397 
398 	/**
399 	 * The progress monitor associated with the fetch operation. By default,
400 	 * this is set to <code>NullProgressMonitor</code>
401 	 *
402 	 * @see NullProgressMonitor
403 	 * @param monitor
404 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
405 	 * @return {@code this}
406 	 */
407 	public FetchCommand setProgressMonitor(ProgressMonitor monitor) {
408 		checkCallable();
409 		if (monitor == null) {
410 			monitor = NullProgressMonitor.INSTANCE;
411 		}
412 		this.monitor = monitor;
413 		return this;
414 	}
415 
416 	/**
417 	 * Get list of {@code RefSpec}s
418 	 *
419 	 * @return the ref specs
420 	 */
421 	public List<RefSpec> getRefSpecs() {
422 		return refSpecs;
423 	}
424 
425 	/**
426 	 * The ref specs to be used in the fetch operation
427 	 *
428 	 * @param specs
429 	 *            String representation of {@code RefSpec}s
430 	 * @return {@code this}
431 	 * @since 4.9
432 	 */
433 	public FetchCommand setRefSpecs(String... specs) {
434 		return setRefSpecs(
435 				Arrays.stream(specs).map(RefSpec::new).collect(toList()));
436 	}
437 
438 	/**
439 	 * The ref specs to be used in the fetch operation
440 	 *
441 	 * @param specs
442 	 *            one or multiple {@link org.eclipse.jgit.transport.RefSpec}s
443 	 * @return {@code this}
444 	 */
445 	public FetchCommand setRefSpecs(RefSpec... specs) {
446 		return setRefSpecs(Arrays.asList(specs));
447 	}
448 
449 	/**
450 	 * The ref specs to be used in the fetch operation
451 	 *
452 	 * @param specs
453 	 *            list of {@link org.eclipse.jgit.transport.RefSpec}s
454 	 * @return {@code this}
455 	 */
456 	public FetchCommand setRefSpecs(List<RefSpec> specs) {
457 		checkCallable();
458 		this.refSpecs.clear();
459 		this.refSpecs.addAll(specs);
460 		return this;
461 	}
462 
463 	/**
464 	 * Whether to do a dry run
465 	 *
466 	 * @return the dry run preference for the fetch operation
467 	 */
468 	public boolean isDryRun() {
469 		return dryRun;
470 	}
471 
472 	/**
473 	 * Sets whether the fetch operation should be a dry run
474 	 *
475 	 * @param dryRun
476 	 *            whether to do a dry run
477 	 * @return {@code this}
478 	 */
479 	public FetchCommand setDryRun(boolean dryRun) {
480 		checkCallable();
481 		this.dryRun = dryRun;
482 		return this;
483 	}
484 
485 	/**
486 	 * Get thin-pack preference
487 	 *
488 	 * @return the thin-pack preference for fetch operation
489 	 */
490 	public boolean isThin() {
491 		return thin;
492 	}
493 
494 	/**
495 	 * Sets the thin-pack preference for fetch operation.
496 	 *
497 	 * Default setting is Transport.DEFAULT_FETCH_THIN
498 	 *
499 	 * @param thin
500 	 *            the thin-pack preference
501 	 * @return {@code this}
502 	 */
503 	public FetchCommand setThin(boolean thin) {
504 		checkCallable();
505 		this.thin = thin;
506 		return this;
507 	}
508 
509 	/**
510 	 * Sets the specification of annotated tag behavior during fetch
511 	 *
512 	 * @param tagOpt
513 	 *            the {@link org.eclipse.jgit.transport.TagOpt}
514 	 * @return {@code this}
515 	 */
516 	public FetchCommand setTagOpt(TagOpt tagOpt) {
517 		checkCallable();
518 		this.tagOption = tagOpt;
519 		return this;
520 	}
521 
522 	/**
523 	 * Register a progress callback.
524 	 *
525 	 * @param callback
526 	 *            the callback
527 	 * @return {@code this}
528 	 * @since 4.8
529 	 */
530 	public FetchCommand setCallback(Callback callback) {
531 		this.callback = callback;
532 		return this;
533 	}
534 
535 	/**
536 	 * Whether fetch --force option is enabled
537 	 *
538 	 * @return whether refs affected by the fetch are updated forcefully
539 	 * @since 5.0
540 	 */
541 	public boolean isForceUpdate() {
542 		return this.isForceUpdate;
543 	}
544 
545 	/**
546 	 * Set fetch --force option
547 	 *
548 	 * @param force
549 	 *            whether to update refs affected by the fetch forcefully
550 	 * @return this command
551 	 * @since 5.0
552 	 */
553 	public FetchCommand setForceUpdate(boolean force) {
554 		this.isForceUpdate = force;
555 		return this;
556 	}
557 }