View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import java.io.File;
13  import java.io.IOException;
14  import java.text.MessageFormat;
15  
16  import org.eclipse.jgit.api.errors.GitAPIException;
17  import org.eclipse.jgit.api.errors.JGitInternalException;
18  import org.eclipse.jgit.api.errors.NoFilepatternException;
19  import org.eclipse.jgit.errors.ConfigInvalidException;
20  import org.eclipse.jgit.internal.JGitText;
21  import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
22  import org.eclipse.jgit.lib.ConfigConstants;
23  import org.eclipse.jgit.lib.Constants;
24  import org.eclipse.jgit.lib.NullProgressMonitor;
25  import org.eclipse.jgit.lib.ProgressMonitor;
26  import org.eclipse.jgit.lib.Repository;
27  import org.eclipse.jgit.lib.StoredConfig;
28  import org.eclipse.jgit.storage.file.FileBasedConfig;
29  import org.eclipse.jgit.submodule.SubmoduleWalk;
30  import org.eclipse.jgit.treewalk.filter.PathFilter;
31  import org.eclipse.jgit.treewalk.filter.TreeFilter;
32  
33  /**
34   * A class used to execute a submodule add command.
35   *
36   * This will clone the configured submodule, register the submodule in the
37   * .gitmodules file and the repository config file, and also add the submodule
38   * and .gitmodules file to the index.
39   *
40   * @see <a href=
41   *      "http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
42   *      >Git documentation about submodules</a>
43   */
44  public class SubmoduleAddCommand extends
45  		TransportCommand<SubmoduleAddCommand, Repository> {
46  
47  	private String name;
48  
49  	private String path;
50  
51  	private String uri;
52  
53  	private ProgressMonitor monitor;
54  
55  	/**
56  	 * Constructor for SubmoduleAddCommand.
57  	 *
58  	 * @param repo
59  	 *            a {@link org.eclipse.jgit.lib.Repository} object.
60  	 */
61  	public SubmoduleAddCommand(Repository repo) {
62  		super(repo);
63  	}
64  
65  	/**
66  	 * Set the submodule name
67  	 *
68  	 * @param name
69  	 * @return this command
70  	 * @since 5.1
71  	 */
72  	public SubmoduleAddCommand setName(String name) {
73  		this.name = name;
74  		return this;
75  	}
76  
77  	/**
78  	 * Set repository-relative path of submodule
79  	 *
80  	 * @param path
81  	 *            (with <code>/</code> as separator)
82  	 * @return this command
83  	 */
84  	public SubmoduleAddCommand setPath(String path) {
85  		this.path = path;
86  		return this;
87  	}
88  
89  	/**
90  	 * Set URI to clone submodule from
91  	 *
92  	 * @param uri
93  	 *            a {@link java.lang.String} object.
94  	 * @return this command
95  	 */
96  	public SubmoduleAddCommand setURI(String uri) {
97  		this.uri = uri;
98  		return this;
99  	}
100 
101 	/**
102 	 * The progress monitor associated with the clone operation. By default,
103 	 * this is set to <code>NullProgressMonitor</code>
104 	 *
105 	 * @see NullProgressMonitor
106 	 * @param monitor
107 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor} object.
108 	 * @return this command
109 	 */
110 	public SubmoduleAddCommand setProgressMonitor(ProgressMonitor monitor) {
111 		this.monitor = monitor;
112 		return this;
113 	}
114 
115 	/**
116 	 * Is the configured already a submodule in the index?
117 	 *
118 	 * @return true if submodule exists in index, false otherwise
119 	 * @throws java.io.IOException
120 	 */
121 	protected boolean submoduleExists() throws IOException {
122 		TreeFilter filter = PathFilter.create(path);
123 		try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) {
124 			return w.setFilter(filter).next();
125 		}
126 	}
127 
128 	/**
129 	 * {@inheritDoc}
130 	 * <p>
131 	 * Executes the {@code SubmoduleAddCommand}
132 	 *
133 	 * The {@code Repository} instance returned by this command needs to be
134 	 * closed by the caller to free resources held by the {@code Repository}
135 	 * instance. It is recommended to call this method as soon as you don't need
136 	 * a reference to this {@code Repository} instance anymore.
137 	 */
138 	@Override
139 	public Repository call() throws GitAPIException {
140 		checkCallable();
141 		if (path == null || path.length() == 0)
142 			throw new IllegalArgumentException(JGitText.get().pathNotConfigured);
143 		if (uri == null || uri.length() == 0)
144 			throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
145 		if (name == null || name.length() == 0) {
146 			// Use the path as the default.
147 			name = path;
148 		}
149 
150 		try {
151 			SubmoduleValidator.assertValidSubmoduleName(name);
152 			SubmoduleValidator.assertValidSubmodulePath(path);
153 			SubmoduleValidator.assertValidSubmoduleUri(uri);
154 		} catch (SubmoduleValidator.SubmoduleValidationException e) {
155 			throw new IllegalArgumentException(e.getMessage());
156 		}
157 
158 		try {
159 			if (submoduleExists())
160 				throw new JGitInternalException(MessageFormat.format(
161 						JGitText.get().submoduleExists, path));
162 		} catch (IOException e) {
163 			throw new JGitInternalException(e.getMessage(), e);
164 		}
165 
166 		final String resolvedUri;
167 		try {
168 			resolvedUri = SubmoduleWalk.getSubmoduleRemoteUrl(repo, uri);
169 		} catch (IOException e) {
170 			throw new JGitInternalException(e.getMessage(), e);
171 		}
172 		// Clone submodule repository
173 		File moduleDirectory = SubmoduleWalk.getSubmoduleDirectory(repo, path);
174 		CloneCommand clone = Git.cloneRepository();
175 		configure(clone);
176 		clone.setDirectory(moduleDirectory);
177 		clone.setGitDir(new File(new File(repo.getDirectory(),
178 				Constants.MODULES), path));
179 		clone.setURI(resolvedUri);
180 		if (monitor != null)
181 			clone.setProgressMonitor(monitor);
182 		Repository subRepo = null;
183 		try (Git git = clone.call()) {
184 			subRepo = git.getRepository();
185 			subRepo.incrementOpen();
186 		}
187 
188 		// Save submodule URL to parent repository's config
189 		StoredConfig config = repo.getConfig();
190 		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
191 				ConfigConstants.CONFIG_KEY_URL, resolvedUri);
192 		try {
193 			config.save();
194 		} catch (IOException e) {
195 			throw new JGitInternalException(e.getMessage(), e);
196 		}
197 
198 		// Save path and URL to parent repository's .gitmodules file
199 		FileBasedConfig modulesConfig = new FileBasedConfig(new File(
200 				repo.getWorkTree(), Constants.DOT_GIT_MODULES), repo.getFS());
201 		try {
202 			modulesConfig.load();
203 			modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
204 					name, ConfigConstants.CONFIG_KEY_PATH, path);
205 			modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
206 					name, ConfigConstants.CONFIG_KEY_URL, uri);
207 			modulesConfig.save();
208 		} catch (IOException | ConfigInvalidException e) {
209 			throw new JGitInternalException(e.getMessage(), e);
210 		}
211 
212 		AddCommand add = new AddCommand(repo);
213 		// Add .gitmodules file to parent repository's index
214 		add.addFilepattern(Constants.DOT_GIT_MODULES);
215 		// Add submodule directory to parent repository's index
216 		add.addFilepattern(path);
217 		try {
218 			add.call();
219 		} catch (NoFilepatternException e) {
220 			throw new JGitInternalException(e.getMessage(), e);
221 		}
222 
223 		return subRepo;
224 	}
225 }