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  	/**
93  	 * @param repo
94  	 */
95  	public SubmoduleUpdateCommand(final Repository repo) {
96  		super(repo);
97  		paths = new ArrayList<String>();
98  	}
99  
100 	/**
101 	 * The progress monitor associated with the clone operation. By default,
102 	 * this is set to <code>NullProgressMonitor</code>
103 	 *
104 	 * @see NullProgressMonitor
105 	 * @param monitor
106 	 * @return this command
107 	 */
108 	public SubmoduleUpdateCommand setProgressMonitor(
109 			final ProgressMonitor monitor) {
110 		this.monitor = monitor;
111 		return this;
112 	}
113 
114 	/**
115 	 * Add repository-relative submodule path to initialize
116 	 *
117 	 * @param path
118 	 *            (with <code>/</code> as separator)
119 	 * @return this command
120 	 */
121 	public SubmoduleUpdateCommand addPath(final String path) {
122 		paths.add(path);
123 		return this;
124 	}
125 
126 	/**
127 	 * Execute the SubmoduleUpdateCommand command.
128 	 *
129 	 * @return a collection of updated submodule paths
130 	 * @throws ConcurrentRefUpdateException
131 	 * @throws CheckoutConflictException
132 	 * @throws InvalidMergeHeadsException
133 	 * @throws InvalidConfigurationException
134 	 * @throws NoHeadException
135 	 * @throws NoMessageException
136 	 * @throws RefNotFoundException
137 	 * @throws WrongRepositoryStateException
138 	 * @throws GitAPIException
139 	 */
140 	public Collection<String> call() throws InvalidConfigurationException,
141 			NoHeadException, ConcurrentRefUpdateException,
142 			CheckoutConflictException, InvalidMergeHeadsException,
143 			WrongRepositoryStateException, NoMessageException, NoHeadException,
144 			RefNotFoundException, GitAPIException {
145 		checkCallable();
146 
147 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
148 			if (!paths.isEmpty())
149 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
150 			List<String> updated = new ArrayList<String>();
151 			while (generator.next()) {
152 				// Skip submodules not registered in .gitmodules file
153 				if (generator.getModulesPath() == null)
154 					continue;
155 				// Skip submodules not registered in parent repository's config
156 				String url = generator.getConfigUrl();
157 				if (url == null)
158 					continue;
159 
160 				Repository submoduleRepo = generator.getRepository();
161 				// Clone repository is not present
162 				if (submoduleRepo == null) {
163 					CloneCommand clone = Git.cloneRepository();
164 					configure(clone);
165 					clone.setURI(url);
166 					clone.setDirectory(generator.getDirectory());
167 					clone.setGitDir(new File(new File(repo.getDirectory(),
168 							Constants.MODULES), generator.getPath()));
169 					if (monitor != null)
170 						clone.setProgressMonitor(monitor);
171 					submoduleRepo = clone.call().getRepository();
172 				}
173 
174 				try (RevWalk walk = new RevWalk(submoduleRepo)) {
175 					RevCommit commit = walk
176 							.parseCommit(generator.getObjectId());
177 
178 					String update = generator.getConfigUpdate();
179 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
180 						MergeCommand merge = new MergeCommand(submoduleRepo);
181 						merge.include(commit);
182 						merge.setProgressMonitor(monitor);
183 						merge.setStrategy(strategy);
184 						merge.call();
185 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
186 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
187 						rebase.setUpstream(commit);
188 						rebase.setProgressMonitor(monitor);
189 						rebase.setStrategy(strategy);
190 						rebase.call();
191 					} else {
192 						// Checkout commit referenced in parent repository's
193 						// index as a detached HEAD
194 						DirCacheCheckout co = new DirCacheCheckout(
195 								submoduleRepo, submoduleRepo.lockDirCache(),
196 								commit.getTree());
197 						co.setFailOnConflict(true);
198 						co.checkout();
199 						RefUpdate refUpdate = submoduleRepo.updateRef(
200 								Constants.HEAD, true);
201 						refUpdate.setNewObjectId(commit);
202 						refUpdate.forceUpdate();
203 					}
204 				} finally {
205 					submoduleRepo.close();
206 				}
207 				updated.add(generator.getPath());
208 			}
209 			return updated;
210 		} catch (IOException e) {
211 			throw new JGitInternalException(e.getMessage(), e);
212 		} catch (ConfigInvalidException e) {
213 			throw new InvalidConfigurationException(e.getMessage(), e);
214 		}
215 	}
216 
217 	/**
218 	 * @param strategy
219 	 *            The merge strategy to use during this update operation.
220 	 * @return {@code this}
221 	 * @since 3.4
222 	 */
223 	public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
224 		this.strategy = strategy;
225 		return this;
226 	}
227 }