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