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 (SubmoduleWalkuleWalk.html#SubmoduleWalk">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.getObjectDatabase()
197 									.has(walk.getObjectId()))
198 							|| recurseMode == FetchRecurseSubmodulesMode.YES) {
199 						FetchCommand f = new FetchCommand(submoduleRepo)
200 								.setProgressMonitor(monitor)
201 								.setTagOpt(tagOption)
202 								.setCheckFetchedObjects(checkFetchedObjects)
203 								.setRemoveDeletedRefs(isRemoveDeletedRefs())
204 								.setThin(thin)
205 								.setRefSpecs(applyOptions(refSpecs))
206 								.setDryRun(dryRun)
207 								.setRecurseSubmodules(recurseMode);
208 						configure(f);
209 						if (callback != null) {
210 							callback.fetchingSubmodule(walk.getPath());
211 						}
212 						results.addSubmodule(walk.getPath(), f.call());
213 					}
214 				}
215 			}
216 		} catch (IOException e) {
217 			throw new JGitInternalException(e.getMessage(), e);
218 		} catch (ConfigInvalidException e) {
219 			throw new InvalidConfigurationException(e.getMessage(), e);
220 		}
221 	}
222 
223 	/**
224 	 * {@inheritDoc}
225 	 * <p>
226 	 * Execute the {@code fetch} command with all the options and parameters
227 	 * collected by the setter methods of this class. Each instance of this
228 	 * class should only be used for one invocation of the command (means: one
229 	 * call to {@link #call()})
230 	 */
231 	@Override
232 	public FetchResult call() throws GitAPIException, InvalidRemoteException,
233 			org.eclipse.jgit.api.errors.TransportException {
234 		checkCallable();
235 
236 		try (Transport transport = Transport.open(repo, remote)) {
237 			transport.setCheckFetchedObjects(checkFetchedObjects);
238 			transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
239 			transport.setDryRun(dryRun);
240 			if (tagOption != null)
241 				transport.setTagOpt(tagOption);
242 			transport.setFetchThin(thin);
243 			configure(transport);
244 			FetchResult result = transport.fetch(monitor,
245 					applyOptions(refSpecs));
246 			if (!repo.isBare()) {
247 				fetchSubmodules(result);
248 			}
249 
250 			return result;
251 		} catch (NoRemoteRepositoryException e) {
252 			throw new InvalidRemoteException(MessageFormat.format(
253 					JGitText.get().invalidRemote, remote), e);
254 		} catch (TransportException e) {
255 			throw new org.eclipse.jgit.api.errors.TransportException(
256 					e.getMessage(), e);
257 		} catch (URISyntaxException e) {
258 			throw new InvalidRemoteException(MessageFormat.format(
259 					JGitText.get().invalidRemote, remote));
260 		} catch (NotSupportedException e) {
261 			throw new JGitInternalException(
262 					JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
263 					e);
264 		}
265 
266 	}
267 
268 	private List<RefSpec> applyOptions(List<RefSpec> refSpecs2) {
269 		if (!isForceUpdate()) {
270 			return refSpecs2;
271 		}
272 		List<RefSpec> updated = new ArrayList<>(3);
273 		for (RefSpec refSpec : refSpecs2) {
274 			updated.add(refSpec.setForceUpdate(true));
275 		}
276 		return updated;
277 	}
278 
279 	/**
280 	 * Set the mode to be used for recursing into submodules.
281 	 *
282 	 * @param recurse
283 	 *            corresponds to the
284 	 *            --recurse-submodules/--no-recurse-submodules options. If
285 	 *            {@code null} use the value of the
286 	 *            {@code submodule.name.fetchRecurseSubmodules} option
287 	 *            configured per submodule. If not specified there, use the
288 	 *            value of the {@code fetch.recurseSubmodules} option configured
289 	 *            in git config. If not configured in either, "on-demand" is the
290 	 *            built-in default.
291 	 * @return {@code this}
292 	 * @since 4.7
293 	 */
294 	public FetchCommand setRecurseSubmodules(
295 			@Nullable FetchRecurseSubmodulesMode recurse) {
296 		checkCallable();
297 		submoduleRecurseMode = recurse;
298 		return this;
299 	}
300 
301 	/**
302 	 * The remote (uri or name) used for the fetch operation. If no remote is
303 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
304 	 * be used.
305 	 *
306 	 * @see Constants#DEFAULT_REMOTE_NAME
307 	 * @param remote
308 	 *            name of a remote
309 	 * @return {@code this}
310 	 */
311 	public FetchCommand setRemote(String remote) {
312 		checkCallable();
313 		this.remote = remote;
314 		return this;
315 	}
316 
317 	/**
318 	 * Get the remote
319 	 *
320 	 * @return the remote used for the remote operation
321 	 */
322 	public String getRemote() {
323 		return remote;
324 	}
325 
326 	/**
327 	 * Get timeout
328 	 *
329 	 * @return the timeout used for the fetch operation
330 	 */
331 	public int getTimeout() {
332 		return timeout;
333 	}
334 
335 	/**
336 	 * Whether to check received objects for validity
337 	 *
338 	 * @return whether to check received objects for validity
339 	 */
340 	public boolean isCheckFetchedObjects() {
341 		return checkFetchedObjects;
342 	}
343 
344 	/**
345 	 * If set to {@code true}, objects received will be checked for validity
346 	 *
347 	 * @param checkFetchedObjects
348 	 *            whether to check objects for validity
349 	 * @return {@code this}
350 	 */
351 	public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) {
352 		checkCallable();
353 		this.checkFetchedObjects = checkFetchedObjects;
354 		return this;
355 	}
356 
357 	/**
358 	 * Whether to remove refs which no longer exist in the source
359 	 *
360 	 * @return whether to remove refs which no longer exist in the source
361 	 */
362 	public boolean isRemoveDeletedRefs() {
363 		if (removeDeletedRefs != null)
364 			return removeDeletedRefs.booleanValue();
365 		else { // fall back to configuration
366 			boolean result = false;
367 			StoredConfig config = repo.getConfig();
368 			result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION,
369 					null, ConfigConstants.CONFIG_KEY_PRUNE, result);
370 			result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
371 					remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
372 			return result;
373 		}
374 	}
375 
376 	/**
377 	 * If set to {@code true}, refs are removed which no longer exist in the
378 	 * source
379 	 *
380 	 * @param removeDeletedRefs
381 	 *            whether to remove deleted {@code Ref}s
382 	 * @return {@code this}
383 	 */
384 	public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) {
385 		checkCallable();
386 		this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs);
387 		return this;
388 	}
389 
390 	/**
391 	 * Get progress monitor
392 	 *
393 	 * @return the progress monitor for the fetch operation
394 	 */
395 	public ProgressMonitor getProgressMonitor() {
396 		return monitor;
397 	}
398 
399 	/**
400 	 * The progress monitor associated with the fetch operation. By default,
401 	 * this is set to <code>NullProgressMonitor</code>
402 	 *
403 	 * @see NullProgressMonitor
404 	 * @param monitor
405 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
406 	 * @return {@code this}
407 	 */
408 	public FetchCommand setProgressMonitor(ProgressMonitor monitor) {
409 		checkCallable();
410 		if (monitor == null) {
411 			monitor = NullProgressMonitor.INSTANCE;
412 		}
413 		this.monitor = monitor;
414 		return this;
415 	}
416 
417 	/**
418 	 * Get list of {@code RefSpec}s
419 	 *
420 	 * @return the ref specs
421 	 */
422 	public List<RefSpec> getRefSpecs() {
423 		return refSpecs;
424 	}
425 
426 	/**
427 	 * The ref specs to be used in the fetch operation
428 	 *
429 	 * @param specs
430 	 *            String representation of {@code RefSpec}s
431 	 * @return {@code this}
432 	 * @since 4.9
433 	 */
434 	public FetchCommand setRefSpecs(String... specs) {
435 		return setRefSpecs(
436 				Arrays.stream(specs).map(RefSpec::new).collect(toList()));
437 	}
438 
439 	/**
440 	 * The ref specs to be used in the fetch operation
441 	 *
442 	 * @param specs
443 	 *            one or multiple {@link org.eclipse.jgit.transport.RefSpec}s
444 	 * @return {@code this}
445 	 */
446 	public FetchCommand setRefSpecs(RefSpec... specs) {
447 		return setRefSpecs(Arrays.asList(specs));
448 	}
449 
450 	/**
451 	 * The ref specs to be used in the fetch operation
452 	 *
453 	 * @param specs
454 	 *            list of {@link org.eclipse.jgit.transport.RefSpec}s
455 	 * @return {@code this}
456 	 */
457 	public FetchCommand setRefSpecs(List<RefSpec> specs) {
458 		checkCallable();
459 		this.refSpecs.clear();
460 		this.refSpecs.addAll(specs);
461 		return this;
462 	}
463 
464 	/**
465 	 * Whether to do a dry run
466 	 *
467 	 * @return the dry run preference for the fetch operation
468 	 */
469 	public boolean isDryRun() {
470 		return dryRun;
471 	}
472 
473 	/**
474 	 * Sets whether the fetch operation should be a dry run
475 	 *
476 	 * @param dryRun
477 	 *            whether to do a dry run
478 	 * @return {@code this}
479 	 */
480 	public FetchCommand setDryRun(boolean dryRun) {
481 		checkCallable();
482 		this.dryRun = dryRun;
483 		return this;
484 	}
485 
486 	/**
487 	 * Get thin-pack preference
488 	 *
489 	 * @return the thin-pack preference for fetch operation
490 	 */
491 	public boolean isThin() {
492 		return thin;
493 	}
494 
495 	/**
496 	 * Sets the thin-pack preference for fetch operation.
497 	 *
498 	 * Default setting is Transport.DEFAULT_FETCH_THIN
499 	 *
500 	 * @param thin
501 	 *            the thin-pack preference
502 	 * @return {@code this}
503 	 */
504 	public FetchCommand setThin(boolean thin) {
505 		checkCallable();
506 		this.thin = thin;
507 		return this;
508 	}
509 
510 	/**
511 	 * Sets the specification of annotated tag behavior during fetch
512 	 *
513 	 * @param tagOpt
514 	 *            the {@link org.eclipse.jgit.transport.TagOpt}
515 	 * @return {@code this}
516 	 */
517 	public FetchCommand setTagOpt(TagOpt tagOpt) {
518 		checkCallable();
519 		this.tagOption = tagOpt;
520 		return this;
521 	}
522 
523 	/**
524 	 * Register a progress callback.
525 	 *
526 	 * @param callback
527 	 *            the callback
528 	 * @return {@code this}
529 	 * @since 4.8
530 	 */
531 	public FetchCommand setCallback(Callback callback) {
532 		this.callback = callback;
533 		return this;
534 	}
535 
536 	/**
537 	 * Whether fetch --force option is enabled
538 	 *
539 	 * @return whether refs affected by the fetch are updated forcefully
540 	 * @since 5.0
541 	 */
542 	public boolean isForceUpdate() {
543 		return this.isForceUpdate;
544 	}
545 
546 	/**
547 	 * Set fetch --force option
548 	 *
549 	 * @param force
550 	 *            whether to update refs affected by the fetch forcefully
551 	 * @return this command
552 	 * @since 5.0
553 	 */
554 	public FetchCommand setForceUpdate(boolean force) {
555 		this.isForceUpdate = force;
556 		return this;
557 	}
558 }