View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc.
3    * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  package org.eclipse.jgit.api;
12  
13  import java.io.File;
14  import java.io.IOException;
15  import java.util.ArrayList;
16  import java.util.Collection;
17  import java.util.List;
18  
19  import org.eclipse.jgit.api.errors.CheckoutConflictException;
20  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
21  import org.eclipse.jgit.api.errors.GitAPIException;
22  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
23  import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
24  import org.eclipse.jgit.api.errors.JGitInternalException;
25  import org.eclipse.jgit.api.errors.NoHeadException;
26  import org.eclipse.jgit.api.errors.NoMessageException;
27  import org.eclipse.jgit.api.errors.RefNotFoundException;
28  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
29  import org.eclipse.jgit.dircache.DirCacheCheckout;
30  import org.eclipse.jgit.errors.ConfigInvalidException;
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.ProgressMonitor;
35  import org.eclipse.jgit.lib.RefUpdate;
36  import org.eclipse.jgit.lib.Repository;
37  import org.eclipse.jgit.merge.MergeStrategy;
38  import org.eclipse.jgit.revwalk.RevCommit;
39  import org.eclipse.jgit.revwalk.RevWalk;
40  import org.eclipse.jgit.submodule.SubmoduleWalk;
41  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
42  
43  /**
44   * A class used to execute a submodule update command.
45   *
46   * @see <a
47   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
48   *      >Git documentation about submodules</a>
49   */
50  public class SubmoduleUpdateCommand extends
51  		TransportCommand<SubmoduleUpdateCommand, Collection<String>> {
52  
53  	private ProgressMonitor monitor;
54  
55  	private final Collection<String> paths;
56  
57  	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
58  
59  	private CloneCommand.Callback callback;
60  
61  	private FetchCommand.Callback fetchCallback;
62  
63  	private boolean fetch = false;
64  
65  	/**
66  	 * <p>
67  	 * Constructor for SubmoduleUpdateCommand.
68  	 * </p>
69  	 *
70  	 * @param repo
71  	 *            a {@link org.eclipse.jgit.lib.Repository} object.
72  	 */
73  	public SubmoduleUpdateCommand(Repository repo) {
74  		super(repo);
75  		paths = new ArrayList<>();
76  	}
77  
78  	/**
79  	 * The progress monitor associated with the clone operation. By default,
80  	 * this is set to <code>NullProgressMonitor</code>
81  	 *
82  	 * @see NullProgressMonitor
83  	 * @param monitor
84  	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor} object.
85  	 * @return this command
86  	 */
87  	public SubmoduleUpdateCommand setProgressMonitor(
88  			final ProgressMonitor monitor) {
89  		this.monitor = monitor;
90  		return this;
91  	}
92  
93  	/**
94  	 * Whether to fetch the submodules before we update them. By default, this
95  	 * is set to <code>false</code>
96  	 *
97  	 * @param fetch
98  	 *            whether to fetch the submodules before we update them
99  	 * @return this command
100 	 * @since 4.9
101 	 */
102 	public SubmoduleUpdateCommand setFetch(boolean fetch) {
103 		this.fetch = fetch;
104 		return this;
105 	}
106 
107 	/**
108 	 * Add repository-relative submodule path to initialize
109 	 *
110 	 * @param path
111 	 *            (with <code>/</code> as separator)
112 	 * @return this command
113 	 */
114 	public SubmoduleUpdateCommand addPath(String path) {
115 		paths.add(path);
116 		return this;
117 	}
118 
119 	private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
120 			throws IOException, GitAPIException {
121 		Repository repository = generator.getRepository();
122 		if (repository == null) {
123 			if (callback != null) {
124 				callback.cloningSubmodule(generator.getPath());
125 			}
126 			CloneCommand clone = Git.cloneRepository();
127 			configure(clone);
128 			clone.setURI(url);
129 			clone.setDirectory(generator.getDirectory());
130 			clone.setGitDir(
131 					new File(new File(repo.getDirectory(), Constants.MODULES),
132 							generator.getPath()));
133 			if (monitor != null) {
134 				clone.setProgressMonitor(monitor);
135 			}
136 			repository = clone.call().getRepository();
137 		} else if (this.fetch) {
138 			if (fetchCallback != null) {
139 				fetchCallback.fetchingSubmodule(generator.getPath());
140 			}
141 			FetchCommand fetchCommand = Git.wrap(repository).fetch();
142 			if (monitor != null) {
143 				fetchCommand.setProgressMonitor(monitor);
144 			}
145 			configure(fetchCommand);
146 			fetchCommand.call();
147 		}
148 		return repository;
149 	}
150 
151 	/**
152 	 * {@inheritDoc}
153 	 *
154 	 * Execute the SubmoduleUpdateCommand command.
155 	 */
156 	@Override
157 	public Collection<String> call() throws InvalidConfigurationException,
158 			NoHeadException, ConcurrentRefUpdateException,
159 			CheckoutConflictException, InvalidMergeHeadsException,
160 			WrongRepositoryStateException, NoMessageException, NoHeadException,
161 			RefNotFoundException, GitAPIException {
162 		checkCallable();
163 
164 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
165 			if (!paths.isEmpty())
166 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
167 			List<String> updated = new ArrayList<>();
168 			while (generator.next()) {
169 				// Skip submodules not registered in .gitmodules file
170 				if (generator.getModulesPath() == null)
171 					continue;
172 				// Skip submodules not registered in parent repository's config
173 				String url = generator.getConfigUrl();
174 				if (url == null)
175 					continue;
176 
177 				try (Repository submoduleRepo = getOrCloneSubmodule(generator,
178 						url); RevWalkvWalk.html#RevWalk">RevWalk walk = new RevWalk(submoduleRepo)) {
179 					RevCommit commit = walk
180 							.parseCommit(generator.getObjectId());
181 
182 					String update = generator.getConfigUpdate();
183 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
184 						MergeCommand merge = new MergeCommand(submoduleRepo);
185 						merge.include(commit);
186 						merge.setProgressMonitor(monitor);
187 						merge.setStrategy(strategy);
188 						merge.call();
189 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
190 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
191 						rebase.setUpstream(commit);
192 						rebase.setProgressMonitor(monitor);
193 						rebase.setStrategy(strategy);
194 						rebase.call();
195 					} else {
196 						// Checkout commit referenced in parent repository's
197 						// index as a detached HEAD
198 						DirCacheCheckout co = new DirCacheCheckout(
199 								submoduleRepo, submoduleRepo.lockDirCache(),
200 								commit.getTree());
201 						co.setFailOnConflict(true);
202 						co.setProgressMonitor(monitor);
203 						co.checkout();
204 						RefUpdate refUpdate = submoduleRepo.updateRef(
205 								Constants.HEAD, true);
206 						refUpdate.setNewObjectId(commit);
207 						refUpdate.forceUpdate();
208 						if (callback != null) {
209 							callback.checkingOut(commit,
210 									generator.getPath());
211 						}
212 					}
213 				}
214 				updated.add(generator.getPath());
215 			}
216 			return updated;
217 		} catch (IOException e) {
218 			throw new JGitInternalException(e.getMessage(), e);
219 		} catch (ConfigInvalidException e) {
220 			throw new InvalidConfigurationException(e.getMessage(), e);
221 		}
222 	}
223 
224 	/**
225 	 * Setter for the field <code>strategy</code>.
226 	 *
227 	 * @param strategy
228 	 *            The merge strategy to use during this update operation.
229 	 * @return {@code this}
230 	 * @since 3.4
231 	 */
232 	public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
233 		this.strategy = strategy;
234 		return this;
235 	}
236 
237 	/**
238 	 * Set status callback for submodule clone operation.
239 	 *
240 	 * @param callback
241 	 *            the callback
242 	 * @return {@code this}
243 	 * @since 4.8
244 	 */
245 	public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
246 		this.callback = callback;
247 		return this;
248 	}
249 
250 	/**
251 	 * Set status callback for submodule fetch operation.
252 	 *
253 	 * @param callback
254 	 *            the callback
255 	 * @return {@code this}
256 	 * @since 4.9
257 	 */
258 	public SubmoduleUpdateCommand setFetchCallback(
259 			FetchCommand.Callback callback) {
260 		this.fetchCallback = callback;
261 		return this;
262 	}
263 }