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  	 * @param repo
100 	 */
101 	public SubmoduleUpdateCommand(final Repository repo) {
102 		super(repo);
103 		paths = new ArrayList<>();
104 	}
105 
106 	/**
107 	 * The progress monitor associated with the clone operation. By default,
108 	 * this is set to <code>NullProgressMonitor</code>
109 	 *
110 	 * @see NullProgressMonitor
111 	 * @param monitor
112 	 * @return this command
113 	 */
114 	public SubmoduleUpdateCommand setProgressMonitor(
115 			final ProgressMonitor monitor) {
116 		this.monitor = monitor;
117 		return this;
118 	}
119 
120 	/**
121 	 * Whether to fetch the submodules before we update them. By default, this
122 	 * is set to <code>false</code>
123 	 *
124 	 * @param fetch
125 	 * @return this command
126 	 * @since 4.9
127 	 */
128 	public SubmoduleUpdateCommand setFetch(final boolean fetch) {
129 		this.fetch = fetch;
130 		return this;
131 	}
132 
133 	/**
134 	 * Add repository-relative submodule path to initialize
135 	 *
136 	 * @param path
137 	 *            (with <code>/</code> as separator)
138 	 * @return this command
139 	 */
140 	public SubmoduleUpdateCommand addPath(final String path) {
141 		paths.add(path);
142 		return this;
143 	}
144 
145 	/**
146 	 * Execute the SubmoduleUpdateCommand command.
147 	 *
148 	 * @return a collection of updated submodule paths
149 	 * @throws ConcurrentRefUpdateException
150 	 * @throws CheckoutConflictException
151 	 * @throws InvalidMergeHeadsException
152 	 * @throws InvalidConfigurationException
153 	 * @throws NoHeadException
154 	 * @throws NoMessageException
155 	 * @throws RefNotFoundException
156 	 * @throws WrongRepositoryStateException
157 	 * @throws GitAPIException
158 	 */
159 	@Override
160 	public Collection<String> call() throws InvalidConfigurationException,
161 			NoHeadException, ConcurrentRefUpdateException,
162 			CheckoutConflictException, InvalidMergeHeadsException,
163 			WrongRepositoryStateException, NoMessageException, NoHeadException,
164 			RefNotFoundException, GitAPIException {
165 		checkCallable();
166 
167 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
168 			if (!paths.isEmpty())
169 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
170 			List<String> updated = new ArrayList<>();
171 			while (generator.next()) {
172 				// Skip submodules not registered in .gitmodules file
173 				if (generator.getModulesPath() == null)
174 					continue;
175 				// Skip submodules not registered in parent repository's config
176 				String url = generator.getConfigUrl();
177 				if (url == null)
178 					continue;
179 
180 				Repository submoduleRepo = generator.getRepository();
181 				// Clone repository if not present
182 				if (submoduleRepo == null) {
183 					if (callback != null) {
184 						callback.cloningSubmodule(generator.getPath());
185 					}
186 					CloneCommand clone = Git.cloneRepository();
187 					configure(clone);
188 					clone.setURI(url);
189 					clone.setDirectory(generator.getDirectory());
190 					clone.setGitDir(new File(new File(repo.getDirectory(),
191 							Constants.MODULES), generator.getPath()));
192 					if (monitor != null)
193 						clone.setProgressMonitor(monitor);
194 					submoduleRepo = clone.call().getRepository();
195 				} else if (this.fetch) {
196 					if (fetchCallback != null) {
197 						fetchCallback.fetchingSubmodule(generator.getPath());
198 					}
199 					FetchCommand fetchCommand = Git.wrap(submoduleRepo).fetch();
200 					if (monitor != null) {
201 						fetchCommand.setProgressMonitor(monitor);
202 					}
203 					configure(fetchCommand);
204 					fetchCommand.call();
205 				}
206 
207 				try (RevWalk walk = new RevWalk(submoduleRepo)) {
208 					RevCommit commit = walk
209 							.parseCommit(generator.getObjectId());
210 
211 					String update = generator.getConfigUpdate();
212 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
213 						MergeCommand merge = new MergeCommand(submoduleRepo);
214 						merge.include(commit);
215 						merge.setProgressMonitor(monitor);
216 						merge.setStrategy(strategy);
217 						merge.call();
218 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
219 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
220 						rebase.setUpstream(commit);
221 						rebase.setProgressMonitor(monitor);
222 						rebase.setStrategy(strategy);
223 						rebase.call();
224 					} else {
225 						// Checkout commit referenced in parent repository's
226 						// index as a detached HEAD
227 						DirCacheCheckout co = new DirCacheCheckout(
228 								submoduleRepo, submoduleRepo.lockDirCache(),
229 								commit.getTree());
230 						co.setFailOnConflict(true);
231 						co.checkout();
232 						RefUpdate refUpdate = submoduleRepo.updateRef(
233 								Constants.HEAD, true);
234 						refUpdate.setNewObjectId(commit);
235 						refUpdate.forceUpdate();
236 						if (callback != null) {
237 							callback.checkingOut(commit,
238 									generator.getPath());
239 						}
240 					}
241 				} finally {
242 					submoduleRepo.close();
243 				}
244 				updated.add(generator.getPath());
245 			}
246 			return updated;
247 		} catch (IOException e) {
248 			throw new JGitInternalException(e.getMessage(), e);
249 		} catch (ConfigInvalidException e) {
250 			throw new InvalidConfigurationException(e.getMessage(), e);
251 		}
252 	}
253 
254 	/**
255 	 * @param strategy
256 	 *            The merge strategy to use during this update operation.
257 	 * @return {@code this}
258 	 * @since 3.4
259 	 */
260 	public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
261 		this.strategy = strategy;
262 		return this;
263 	}
264 
265 	/**
266 	 * Set status callback for submodule clone operation.
267 	 *
268 	 * @param callback
269 	 *            the callback
270 	 * @return {@code this}
271 	 * @since 4.8
272 	 */
273 	public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
274 		this.callback = callback;
275 		return this;
276 	}
277 
278 	/**
279 	 * Set status callback for submodule fetch operation.
280 	 *
281 	 * @param callback
282 	 *            the callback
283 	 * @return {@code this}
284 	 * @since 4.9
285 	 */
286 	public SubmoduleUpdateCommand setFetchCallback(
287 			FetchCommand.Callback callback) {
288 		this.fetchCallback = callback;
289 		return this;
290 	}
291 }