View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc.
3    * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import java.io.File;
47  import java.io.IOException;
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.List;
51  
52  import org.eclipse.jgit.api.errors.CheckoutConflictException;
53  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
54  import org.eclipse.jgit.api.errors.GitAPIException;
55  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
56  import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
57  import org.eclipse.jgit.api.errors.JGitInternalException;
58  import org.eclipse.jgit.api.errors.NoHeadException;
59  import org.eclipse.jgit.api.errors.NoMessageException;
60  import org.eclipse.jgit.api.errors.RefNotFoundException;
61  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
62  import org.eclipse.jgit.dircache.DirCacheCheckout;
63  import org.eclipse.jgit.errors.ConfigInvalidException;
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.ProgressMonitor;
68  import org.eclipse.jgit.lib.RefUpdate;
69  import org.eclipse.jgit.lib.Repository;
70  import org.eclipse.jgit.merge.MergeStrategy;
71  import org.eclipse.jgit.revwalk.RevCommit;
72  import org.eclipse.jgit.revwalk.RevWalk;
73  import org.eclipse.jgit.submodule.SubmoduleWalk;
74  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
75  
76  /**
77   * A class used to execute a submodule update command.
78   *
79   * @see <a
80   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
81   *      >Git documentation about submodules</a>
82   */
83  public class SubmoduleUpdateCommand extends
84  		TransportCommand<SubmoduleUpdateCommand, Collection<String>> {
85  
86  	private ProgressMonitor monitor;
87  
88  	private final Collection<String> paths;
89  
90  	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
91  
92  	private CloneCommand.Callback callback;
93  
94  	private FetchCommand.Callback fetchCallback;
95  
96  	private boolean fetch = false;
97  
98  	/**
99  	 * <p>
100 	 * Constructor for SubmoduleUpdateCommand.
101 	 * </p>
102 	 *
103 	 * @param repo
104 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
105 	 */
106 	public SubmoduleUpdateCommand(Repository repo) {
107 		super(repo);
108 		paths = new ArrayList<>();
109 	}
110 
111 	/**
112 	 * The progress monitor associated with the clone operation. By default,
113 	 * this is set to <code>NullProgressMonitor</code>
114 	 *
115 	 * @see NullProgressMonitor
116 	 * @param monitor
117 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor} object.
118 	 * @return this command
119 	 */
120 	public SubmoduleUpdateCommand setProgressMonitor(
121 			final ProgressMonitor monitor) {
122 		this.monitor = monitor;
123 		return this;
124 	}
125 
126 	/**
127 	 * Whether to fetch the submodules before we update them. By default, this
128 	 * is set to <code>false</code>
129 	 *
130 	 * @param fetch
131 	 *            whether to fetch the submodules before we update them
132 	 * @return this command
133 	 * @since 4.9
134 	 */
135 	public SubmoduleUpdateCommand setFetch(boolean fetch) {
136 		this.fetch = fetch;
137 		return this;
138 	}
139 
140 	/**
141 	 * Add repository-relative submodule path to initialize
142 	 *
143 	 * @param path
144 	 *            (with <code>/</code> as separator)
145 	 * @return this command
146 	 */
147 	public SubmoduleUpdateCommand addPath(String path) {
148 		paths.add(path);
149 		return this;
150 	}
151 
152 	private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
153 			throws IOException, GitAPIException {
154 		Repository repository = generator.getRepository();
155 		if (repository == null) {
156 			if (callback != null) {
157 				callback.cloningSubmodule(generator.getPath());
158 			}
159 			CloneCommand clone = Git.cloneRepository();
160 			configure(clone);
161 			clone.setURI(url);
162 			clone.setDirectory(generator.getDirectory());
163 			clone.setGitDir(
164 					new File(new File(repo.getDirectory(), Constants.MODULES),
165 							generator.getPath()));
166 			if (monitor != null) {
167 				clone.setProgressMonitor(monitor);
168 			}
169 			repository = clone.call().getRepository();
170 		} else if (this.fetch) {
171 			if (fetchCallback != null) {
172 				fetchCallback.fetchingSubmodule(generator.getPath());
173 			}
174 			FetchCommand fetchCommand = Git.wrap(repository).fetch();
175 			if (monitor != null) {
176 				fetchCommand.setProgressMonitor(monitor);
177 			}
178 			configure(fetchCommand);
179 			fetchCommand.call();
180 		}
181 		return repository;
182 	}
183 
184 	/**
185 	 * {@inheritDoc}
186 	 *
187 	 * Execute the SubmoduleUpdateCommand command.
188 	 */
189 	@Override
190 	public Collection<String> call() throws InvalidConfigurationException,
191 			NoHeadException, ConcurrentRefUpdateException,
192 			CheckoutConflictException, InvalidMergeHeadsException,
193 			WrongRepositoryStateException, NoMessageException, NoHeadException,
194 			RefNotFoundException, GitAPIException {
195 		checkCallable();
196 
197 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
198 			if (!paths.isEmpty())
199 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
200 			List<String> updated = new ArrayList<>();
201 			while (generator.next()) {
202 				// Skip submodules not registered in .gitmodules file
203 				if (generator.getModulesPath() == null)
204 					continue;
205 				// Skip submodules not registered in parent repository's config
206 				String url = generator.getConfigUrl();
207 				if (url == null)
208 					continue;
209 
210 				try (Repository submoduleRepo = getOrCloneSubmodule(generator,
211 						url); RevWalk walk = new RevWalk(submoduleRepo)) {
212 					RevCommit commit = walk
213 							.parseCommit(generator.getObjectId());
214 
215 					String update = generator.getConfigUpdate();
216 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
217 						MergeCommand merge = new MergeCommand(submoduleRepo);
218 						merge.include(commit);
219 						merge.setProgressMonitor(monitor);
220 						merge.setStrategy(strategy);
221 						merge.call();
222 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
223 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
224 						rebase.setUpstream(commit);
225 						rebase.setProgressMonitor(monitor);
226 						rebase.setStrategy(strategy);
227 						rebase.call();
228 					} else {
229 						// Checkout commit referenced in parent repository's
230 						// index as a detached HEAD
231 						DirCacheCheckout co = new DirCacheCheckout(
232 								submoduleRepo, submoduleRepo.lockDirCache(),
233 								commit.getTree());
234 						co.setFailOnConflict(true);
235 						co.setProgressMonitor(monitor);
236 						co.checkout();
237 						RefUpdate refUpdate = submoduleRepo.updateRef(
238 								Constants.HEAD, true);
239 						refUpdate.setNewObjectId(commit);
240 						refUpdate.forceUpdate();
241 						if (callback != null) {
242 							callback.checkingOut(commit,
243 									generator.getPath());
244 						}
245 					}
246 				}
247 				updated.add(generator.getPath());
248 			}
249 			return updated;
250 		} catch (IOException e) {
251 			throw new JGitInternalException(e.getMessage(), e);
252 		} catch (ConfigInvalidException e) {
253 			throw new InvalidConfigurationException(e.getMessage(), e);
254 		}
255 	}
256 
257 	/**
258 	 * Setter for the field <code>strategy</code>.
259 	 *
260 	 * @param strategy
261 	 *            The merge strategy to use during this update operation.
262 	 * @return {@code this}
263 	 * @since 3.4
264 	 */
265 	public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
266 		this.strategy = strategy;
267 		return this;
268 	}
269 
270 	/**
271 	 * Set status callback for submodule clone operation.
272 	 *
273 	 * @param callback
274 	 *            the callback
275 	 * @return {@code this}
276 	 * @since 4.8
277 	 */
278 	public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
279 		this.callback = callback;
280 		return this;
281 	}
282 
283 	/**
284 	 * Set status callback for submodule fetch operation.
285 	 *
286 	 * @param callback
287 	 *            the callback
288 	 * @return {@code this}
289 	 * @since 4.9
290 	 */
291 	public SubmoduleUpdateCommand setFetchCallback(
292 			FetchCommand.Callback callback) {
293 		this.fetchCallback = callback;
294 		return this;
295 	}
296 }