View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc.
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 java.io.File;
46  import java.io.IOException;
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.List;
50  
51  import org.eclipse.jgit.api.errors.CheckoutConflictException;
52  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
53  import org.eclipse.jgit.api.errors.GitAPIException;
54  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
55  import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
56  import org.eclipse.jgit.api.errors.JGitInternalException;
57  import org.eclipse.jgit.api.errors.NoHeadException;
58  import org.eclipse.jgit.api.errors.NoMessageException;
59  import org.eclipse.jgit.api.errors.RefNotFoundException;
60  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
61  import org.eclipse.jgit.dircache.DirCacheCheckout;
62  import org.eclipse.jgit.errors.ConfigInvalidException;
63  import org.eclipse.jgit.lib.ConfigConstants;
64  import org.eclipse.jgit.lib.Constants;
65  import org.eclipse.jgit.lib.NullProgressMonitor;
66  import org.eclipse.jgit.lib.ProgressMonitor;
67  import org.eclipse.jgit.lib.RefUpdate;
68  import org.eclipse.jgit.lib.Repository;
69  import org.eclipse.jgit.merge.MergeStrategy;
70  import org.eclipse.jgit.revwalk.RevCommit;
71  import org.eclipse.jgit.revwalk.RevWalk;
72  import org.eclipse.jgit.submodule.SubmoduleWalk;
73  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
74  
75  /**
76   * A class used to execute a submodule update command.
77   *
78   * @see <a
79   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
80   *      >Git documentation about submodules</a>
81   */
82  public class SubmoduleUpdateCommand extends
83  		TransportCommand<SubmoduleUpdateCommand, Collection<String>> {
84  
85  	private ProgressMonitor monitor;
86  
87  	private final Collection<String> paths;
88  
89  	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
90  
91  	/**
92  	 * @param repo
93  	 */
94  	public SubmoduleUpdateCommand(final Repository repo) {
95  		super(repo);
96  		paths = new ArrayList<String>();
97  	}
98  
99  	/**
100 	 * The progress monitor associated with the clone operation. By default,
101 	 * this is set to <code>NullProgressMonitor</code>
102 	 *
103 	 * @see NullProgressMonitor
104 	 * @param monitor
105 	 * @return this command
106 	 */
107 	public SubmoduleUpdateCommand setProgressMonitor(
108 			final ProgressMonitor monitor) {
109 		this.monitor = monitor;
110 		return this;
111 	}
112 
113 	/**
114 	 * Add repository-relative submodule path to initialize
115 	 *
116 	 * @param path
117 	 *            (with <code>/</code> as separator)
118 	 * @return this command
119 	 */
120 	public SubmoduleUpdateCommand addPath(final String path) {
121 		paths.add(path);
122 		return this;
123 	}
124 
125 	/**
126 	 * Execute the SubmoduleUpdateCommand command.
127 	 *
128 	 * @return a collection of updated submodule paths
129 	 * @throws ConcurrentRefUpdateException
130 	 * @throws CheckoutConflictException
131 	 * @throws InvalidMergeHeadsException
132 	 * @throws InvalidConfigurationException
133 	 * @throws NoHeadException
134 	 * @throws NoMessageException
135 	 * @throws RefNotFoundException
136 	 * @throws WrongRepositoryStateException
137 	 * @throws GitAPIException
138 	 */
139 	public Collection<String> call() throws InvalidConfigurationException,
140 			NoHeadException, ConcurrentRefUpdateException,
141 			CheckoutConflictException, InvalidMergeHeadsException,
142 			WrongRepositoryStateException, NoMessageException, NoHeadException,
143 			RefNotFoundException, GitAPIException {
144 		checkCallable();
145 
146 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
147 			if (!paths.isEmpty())
148 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
149 			List<String> updated = new ArrayList<String>();
150 			while (generator.next()) {
151 				// Skip submodules not registered in .gitmodules file
152 				if (generator.getModulesPath() == null)
153 					continue;
154 				// Skip submodules not registered in parent repository's config
155 				String url = generator.getConfigUrl();
156 				if (url == null)
157 					continue;
158 
159 				Repository submoduleRepo = generator.getRepository();
160 				// Clone repository is not present
161 				if (submoduleRepo == null) {
162 					CloneCommand clone = Git.cloneRepository();
163 					configure(clone);
164 					clone.setURI(url);
165 					clone.setDirectory(generator.getDirectory());
166 					clone.setGitDir(new File(new File(repo.getDirectory(),
167 							Constants.MODULES), generator.getPath()));
168 					if (monitor != null)
169 						clone.setProgressMonitor(monitor);
170 					submoduleRepo = clone.call().getRepository();
171 				}
172 
173 				try (RevWalk walk = new RevWalk(submoduleRepo)) {
174 					RevCommit commit = walk
175 							.parseCommit(generator.getObjectId());
176 
177 					String update = generator.getConfigUpdate();
178 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
179 						MergeCommand merge = new MergeCommand(submoduleRepo);
180 						merge.include(commit);
181 						merge.setStrategy(strategy);
182 						merge.call();
183 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
184 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
185 						rebase.setUpstream(commit);
186 						rebase.setStrategy(strategy);
187 						rebase.call();
188 					} else {
189 						// Checkout commit referenced in parent repository's
190 						// index as a detached HEAD
191 						DirCacheCheckout co = new DirCacheCheckout(
192 								submoduleRepo, submoduleRepo.lockDirCache(),
193 								commit.getTree());
194 						co.setFailOnConflict(true);
195 						co.checkout();
196 						RefUpdate refUpdate = submoduleRepo.updateRef(
197 								Constants.HEAD, true);
198 						refUpdate.setNewObjectId(commit);
199 						refUpdate.forceUpdate();
200 					}
201 				} finally {
202 					submoduleRepo.close();
203 				}
204 				updated.add(generator.getPath());
205 			}
206 			return updated;
207 		} catch (IOException e) {
208 			throw new JGitInternalException(e.getMessage(), e);
209 		} catch (ConfigInvalidException e) {
210 			throw new InvalidConfigurationException(e.getMessage(), e);
211 		}
212 	}
213 
214 	/**
215 	 * @param strategy
216 	 *            The merge strategy to use during this update operation.
217 	 * @return {@code this}
218 	 * @since 3.4
219 	 */
220 	public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
221 		this.strategy = strategy;
222 		return this;
223 	}
224 }