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 	/**
109 	 * Callback for status of fetch operation.
110 	 *
111 	 * @since 4.8
112 	 *
113 	 */
114 	public interface Callback {
115 		/**
116 		 * Notify fetching a submodule.
117 		 *
118 		 * @param name
119 		 *            the submodule name.
120 		 */
121 		void fetchingSubmodule(String name);
122 	}
123 
124 	/**
125 	 * @param repo
126 	 */
127 	protected FetchCommand(Repository repo) {
128 		super(repo);
129 		refSpecs = new ArrayList<>(3);
130 	}
131 
132 	private FetchRecurseSubmodulesMode getRecurseMode(String path) {
133 		// Use the caller-specified mode, if set
134 		if (submoduleRecurseMode != null) {
135 			return submoduleRecurseMode;
136 		}
137 
138 		// Fall back to submodule.name.fetchRecurseSubmodules, if set
139 		FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(
140 				FetchRecurseSubmodulesMode.values(),
141 				ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
142 				ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null);
143 		if (mode != null) {
144 			return mode;
145 		}
146 
147 		// Fall back to fetch.recurseSubmodules, if set
148 		mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
149 				ConfigConstants.CONFIG_FETCH_SECTION, null,
150 				ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null);
151 		if (mode != null) {
152 			return mode;
153 		}
154 
155 		// Default to on-demand mode
156 		return FetchRecurseSubmodulesMode.ON_DEMAND;
157 	}
158 
159 	private void fetchSubmodules(FetchResult results)
160 			throws org.eclipse.jgit.api.errors.TransportException,
161 			GitAPIException, InvalidConfigurationException {
162 		try (SubmoduleWalk walk = new SubmoduleWalk(repo);
163 				RevWalk revWalk = new RevWalk(repo)) {
164 			// Walk over submodules in the parent repository's FETCH_HEAD.
165 			ObjectId fetchHead = repo.resolve(Constants.FETCH_HEAD);
166 			if (fetchHead == null) {
167 				return;
168 			}
169 			walk.setTree(revWalk.parseTree(fetchHead));
170 			while (walk.next()) {
171 				Repository submoduleRepo = walk.getRepository();
172 
173 				// Skip submodules that don't exist locally (have not been
174 				// cloned), are not registered in the .gitmodules file, or
175 				// not registered in the parent repository's config.
176 				if (submoduleRepo == null || walk.getModulesPath() == null
177 						|| walk.getConfigUrl() == null) {
178 					continue;
179 				}
180 
181 				FetchRecurseSubmodulesMode recurseMode = getRecurseMode(
182 						walk.getPath());
183 
184 				// When the fetch mode is "yes" we always fetch. When the mode
185 				// is "on demand", we only fetch if the submodule's revision was
186 				// updated to an object that is not currently present in the
187 				// submodule.
188 				if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND
189 						&& !submoduleRepo.hasObject(walk.getObjectId()))
190 						|| recurseMode == FetchRecurseSubmodulesMode.YES) {
191 					FetchCommand f = new FetchCommand(submoduleRepo)
192 							.setProgressMonitor(monitor).setTagOpt(tagOption)
193 							.setCheckFetchedObjects(checkFetchedObjects)
194 							.setRemoveDeletedRefs(isRemoveDeletedRefs())
195 							.setThin(thin).setRefSpecs(refSpecs)
196 							.setDryRun(dryRun)
197 							.setRecurseSubmodules(recurseMode);
198 					configure(f);
199 					if (callback != null) {
200 						callback.fetchingSubmodule(walk.getPath());
201 					}
202 					results.addSubmodule(walk.getPath(), f.call());
203 				}
204 			}
205 		} catch (IOException e) {
206 			throw new JGitInternalException(e.getMessage(), e);
207 		} catch (ConfigInvalidException e) {
208 			throw new InvalidConfigurationException(e.getMessage(), e);
209 		}
210 	}
211 
212 	/**
213 	 * Executes the {@code fetch} command with all the options and parameters
214 	 * collected by the setter methods of this class. Each instance of this
215 	 * class should only be used for one invocation of the command (means: one
216 	 * call to {@link #call()})
217 	 *
218 	 * @return a {@link FetchResult} object representing the successful fetch
219 	 *         result
220 	 * @throws InvalidRemoteException
221 	 *             when called with an invalid remote uri
222 	 * @throws org.eclipse.jgit.api.errors.TransportException
223 	 *             when an error occurs during transport
224 	 */
225 	@Override
226 	public FetchResult call() throws GitAPIException, InvalidRemoteException,
227 			org.eclipse.jgit.api.errors.TransportException {
228 		checkCallable();
229 
230 		try (Transport transport = Transport.open(repo, remote)) {
231 			transport.setCheckFetchedObjects(checkFetchedObjects);
232 			transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
233 			transport.setDryRun(dryRun);
234 			if (tagOption != null)
235 				transport.setTagOpt(tagOption);
236 			transport.setFetchThin(thin);
237 			configure(transport);
238 
239 			FetchResult result = transport.fetch(monitor, refSpecs);
240 			if (!repo.isBare()) {
241 				fetchSubmodules(result);
242 			}
243 
244 			return result;
245 		} catch (NoRemoteRepositoryException e) {
246 			throw new InvalidRemoteException(MessageFormat.format(
247 					JGitText.get().invalidRemote, remote), e);
248 		} catch (TransportException e) {
249 			throw new org.eclipse.jgit.api.errors.TransportException(
250 					e.getMessage(), e);
251 		} catch (URISyntaxException e) {
252 			throw new InvalidRemoteException(MessageFormat.format(
253 					JGitText.get().invalidRemote, remote));
254 		} catch (NotSupportedException e) {
255 			throw new JGitInternalException(
256 					JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
257 					e);
258 		}
259 
260 	}
261 
262 	/**
263 	 * Set the mode to be used for recursing into submodules.
264 	 *
265 	 * @param recurse
266 	 *            corresponds to the
267 	 *            --recurse-submodules/--no-recurse-submodules options. If
268 	 *            {@code null} use the value of the
269 	 *            {@code submodule.name.fetchRecurseSubmodules} option
270 	 *            configured per submodule. If not specified there, use the
271 	 *            value of the {@code fetch.recurseSubmodules} option configured
272 	 *            in git config. If not configured in either, "on-demand" is the
273 	 *            built-in default.
274 	 * @return {@code this}
275 	 * @since 4.7
276 	 */
277 	public FetchCommand setRecurseSubmodules(
278 			@Nullable FetchRecurseSubmodulesMode recurse) {
279 		checkCallable();
280 		submoduleRecurseMode = recurse;
281 		return this;
282 	}
283 
284 	/**
285 	 * The remote (uri or name) used for the fetch operation. If no remote is
286 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
287 	 * be used.
288 	 *
289 	 * @see Constants#DEFAULT_REMOTE_NAME
290 	 * @param remote
291 	 * @return {@code this}
292 	 */
293 	public FetchCommand setRemote(String remote) {
294 		checkCallable();
295 		this.remote = remote;
296 		return this;
297 	}
298 
299 	/**
300 	 * @return the remote used for the remote operation
301 	 */
302 	public String getRemote() {
303 		return remote;
304 	}
305 
306 	/**
307 	 * @return the timeout used for the fetch operation
308 	 */
309 	public int getTimeout() {
310 		return timeout;
311 	}
312 
313 	/**
314 	 * @return whether to check received objects checked for validity
315 	 */
316 	public boolean isCheckFetchedObjects() {
317 		return checkFetchedObjects;
318 	}
319 
320 	/**
321 	 * If set to true, objects received will be checked for validity
322 	 *
323 	 * @param checkFetchedObjects
324 	 * @return {@code this}
325 	 */
326 	public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) {
327 		checkCallable();
328 		this.checkFetchedObjects = checkFetchedObjects;
329 		return this;
330 	}
331 
332 	/**
333 	 * @return whether or not to remove refs which no longer exist in the source
334 	 */
335 	public boolean isRemoveDeletedRefs() {
336 		if (removeDeletedRefs != null)
337 			return removeDeletedRefs.booleanValue();
338 		else { // fall back to configuration
339 			boolean result = false;
340 			StoredConfig config = repo.getConfig();
341 			result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION,
342 					null, ConfigConstants.CONFIG_KEY_PRUNE, result);
343 			result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
344 					remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
345 			return result;
346 		}
347 	}
348 
349 	/**
350 	 * If set to true, refs are removed which no longer exist in the source
351 	 *
352 	 * @param removeDeletedRefs
353 	 * @return {@code this}
354 	 */
355 	public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) {
356 		checkCallable();
357 		this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs);
358 		return this;
359 	}
360 
361 	/**
362 	 * @return the progress monitor for the fetch operation
363 	 */
364 	public ProgressMonitor getProgressMonitor() {
365 		return monitor;
366 	}
367 
368 	/**
369 	 * The progress monitor associated with the fetch operation. By default,
370 	 * this is set to <code>NullProgressMonitor</code>
371 	 *
372 	 * @see NullProgressMonitor
373 	 *
374 	 * @param monitor
375 	 * @return {@code this}
376 	 */
377 	public FetchCommand setProgressMonitor(ProgressMonitor monitor) {
378 		checkCallable();
379 		if (monitor == null) {
380 			monitor = NullProgressMonitor.INSTANCE;
381 		}
382 		this.monitor = monitor;
383 		return this;
384 	}
385 
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 	 * @return {@code this}
398 	 * @since 4.9
399 	 */
400 	public FetchCommand setRefSpecs(String... specs) {
401 		return setRefSpecs(
402 				Arrays.stream(specs).map(RefSpec::new).collect(toList()));
403 	}
404 
405 	/**
406 	 * The ref specs to be used in the fetch operation
407 	 *
408 	 * @param specs
409 	 * @return {@code this}
410 	 */
411 	public FetchCommand setRefSpecs(RefSpec... specs) {
412 		return setRefSpecs(Arrays.asList(specs));
413 	}
414 
415 	/**
416 	 * The ref specs to be used in the fetch operation
417 	 *
418 	 * @param specs
419 	 * @return {@code this}
420 	 */
421 	public FetchCommand setRefSpecs(List<RefSpec> specs) {
422 		checkCallable();
423 		this.refSpecs.clear();
424 		this.refSpecs.addAll(specs);
425 		return this;
426 	}
427 
428 	/**
429 	 * @return the dry run preference for the fetch operation
430 	 */
431 	public boolean isDryRun() {
432 		return dryRun;
433 	}
434 
435 	/**
436 	 * Sets whether the fetch operation should be a dry run
437 	 *
438 	 * @param dryRun
439 	 * @return {@code this}
440 	 */
441 	public FetchCommand setDryRun(boolean dryRun) {
442 		checkCallable();
443 		this.dryRun = dryRun;
444 		return this;
445 	}
446 
447 	/**
448 	 * @return the thin-pack preference for fetch operation
449 	 */
450 	public boolean isThin() {
451 		return thin;
452 	}
453 
454 	/**
455 	 * Sets the thin-pack preference for fetch operation.
456 	 *
457 	 * Default setting is Transport.DEFAULT_FETCH_THIN
458 	 *
459 	 * @param thin
460 	 * @return {@code this}
461 	 */
462 	public FetchCommand setThin(boolean thin) {
463 		checkCallable();
464 		this.thin = thin;
465 		return this;
466 	}
467 
468 	/**
469 	 * Sets the specification of annotated tag behavior during fetch
470 	 *
471 	 * @param tagOpt
472 	 * @return {@code this}
473 	 */
474 	public FetchCommand setTagOpt(TagOpt tagOpt) {
475 		checkCallable();
476 		this.tagOption = tagOpt;
477 		return this;
478 	}
479 
480 	/**
481 	 * Register a progress callback.
482 	 *
483 	 * @param callback
484 	 *            the callback
485 	 * @return {@code this}
486 	 * @since 4.8
487 	 */
488 	public FetchCommand setCallback(Callback callback) {
489 		this.callback = callback;
490 		return this;
491 	}
492 }