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.submodule;
44  
45  import static java.nio.charset.StandardCharsets.UTF_8;
46  import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
47  import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL;
48  import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SUBMODULE_SECTION;
49  import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
50  import static org.junit.Assert.assertEquals;
51  import static org.junit.Assert.assertFalse;
52  import static org.junit.Assert.assertNotNull;
53  import static org.junit.Assert.assertNull;
54  import static org.junit.Assert.assertTrue;
55  
56  import java.io.BufferedWriter;
57  import java.io.File;
58  import java.io.IOException;
59  import java.nio.file.Files;
60  
61  import org.eclipse.jgit.api.Git;
62  import org.eclipse.jgit.api.Status;
63  import org.eclipse.jgit.api.errors.GitAPIException;
64  import org.eclipse.jgit.dircache.DirCache;
65  import org.eclipse.jgit.dircache.DirCacheEditor;
66  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
67  import org.eclipse.jgit.dircache.DirCacheEntry;
68  import org.eclipse.jgit.errors.ConfigInvalidException;
69  import org.eclipse.jgit.errors.NoWorkTreeException;
70  import org.eclipse.jgit.internal.storage.file.FileRepository;
71  import org.eclipse.jgit.junit.RepositoryTestCase;
72  import org.eclipse.jgit.junit.TestRepository;
73  import org.eclipse.jgit.lib.Config;
74  import org.eclipse.jgit.lib.Constants;
75  import org.eclipse.jgit.lib.FileMode;
76  import org.eclipse.jgit.lib.ObjectId;
77  import org.eclipse.jgit.lib.Repository;
78  import org.eclipse.jgit.revwalk.RevBlob;
79  import org.eclipse.jgit.revwalk.RevCommit;
80  import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
81  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
82  import org.eclipse.jgit.treewalk.filter.PathFilter;
83  import org.junit.Before;
84  import org.junit.Test;
85  
86  /**
87   * Unit tests of {@link SubmoduleWalk}
88   */
89  public class SubmoduleWalkTest extends RepositoryTestCase {
90  	private TestRepository<Repository> testDb;
91  
92  	@Override
93  	@Before
94  	public void setUp() throws Exception {
95  		super.setUp();
96  		testDb = new TestRepository<>(db);
97  	}
98  
99  	@Test
100 	public void repositoryWithNoSubmodules() throws IOException {
101 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
102 		assertFalse(gen.next());
103 		assertNull(gen.getPath());
104 		assertEquals(ObjectId.zeroId(), gen.getObjectId());
105 	}
106 
107 	@Test
108 	public void bareRepositoryWithNoSubmodules() throws IOException {
109 		FileRepository bareRepo = createBareRepository();
110 		boolean result = SubmoduleWalk.containsGitModulesFile(bareRepo);
111 		assertFalse(result);
112 	}
113 
114 	@Test
115 	public void repositoryWithRootLevelSubmodule() throws IOException,
116 			ConfigInvalidException, NoWorkTreeException, GitAPIException {
117 		final ObjectId id = ObjectId
118 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
119 		final String path = "sub";
120 		DirCache cache = db.lockDirCache();
121 		DirCacheEditor editor = cache.editor();
122 		editor.add(new PathEdit(path) {
123 
124 			@Override
125 			public void apply(DirCacheEntry ent) {
126 				ent.setFileMode(FileMode.GITLINK);
127 				ent.setObjectId(id);
128 			}
129 		});
130 		editor.commit();
131 
132 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
133 		assertTrue(gen.next());
134 		assertEquals(path, gen.getPath());
135 		assertEquals(id, gen.getObjectId());
136 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
137 		assertNull(gen.getConfigUpdate());
138 		assertNull(gen.getConfigUrl());
139 		assertNull(gen.getModulesPath());
140 		assertNull(gen.getModulesUpdate());
141 		assertNull(gen.getModulesUrl());
142 		assertNull(gen.getRepository());
143 		Status status = Git.wrap(db).status().call();
144 		assertTrue(!status.isClean());
145 		assertFalse(gen.next());
146 	}
147 
148 	@Test
149 	public void repositoryWithRootLevelSubmoduleAbsoluteRef()
150 			throws IOException, ConfigInvalidException {
151 		final ObjectId id = ObjectId
152 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
153 		final String path = "sub";
154 		File dotGit = new File(db.getWorkTree(), path + File.separatorChar
155 				+ Constants.DOT_GIT);
156 		if (!dotGit.getParentFile().exists())
157 			dotGit.getParentFile().mkdirs();
158 
159 		File modulesGitDir = new File(db.getDirectory(),
160 				"modules" + File.separatorChar + path);
161 		try (BufferedWriter fw = Files.newBufferedWriter(dotGit.toPath(),
162 				UTF_8)) {
163 			fw.append("gitdir: " + modulesGitDir.getAbsolutePath());
164 		}
165 		FileRepositoryBuilder builder = new FileRepositoryBuilder();
166 		builder.setWorkTree(new File(db.getWorkTree(), path));
167 		builder.build().create();
168 
169 		DirCache cache = db.lockDirCache();
170 		DirCacheEditor editor = cache.editor();
171 		editor.add(new PathEdit(path) {
172 
173 			@Override
174 			public void apply(DirCacheEntry ent) {
175 				ent.setFileMode(FileMode.GITLINK);
176 				ent.setObjectId(id);
177 			}
178 		});
179 		editor.commit();
180 
181 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
182 		assertTrue(gen.next());
183 		assertEquals(path, gen.getPath());
184 		assertEquals(id, gen.getObjectId());
185 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
186 		assertNull(gen.getConfigUpdate());
187 		assertNull(gen.getConfigUrl());
188 		assertNull(gen.getModulesPath());
189 		assertNull(gen.getModulesUpdate());
190 		assertNull(gen.getModulesUrl());
191 		try (Repository subRepo = gen.getRepository()) {
192 			assertNotNull(subRepo);
193 			assertEquals(modulesGitDir.getAbsolutePath(),
194 					subRepo.getDirectory().getAbsolutePath());
195 			assertEquals(new File(db.getWorkTree(), path).getAbsolutePath(),
196 					subRepo.getWorkTree().getAbsolutePath());
197 		}
198 		assertFalse(gen.next());
199 	}
200 
201 	@Test
202 	public void repositoryWithRootLevelSubmoduleRelativeRef()
203 			throws IOException, ConfigInvalidException {
204 		final ObjectId id = ObjectId
205 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
206 		final String path = "sub";
207 		File dotGit = new File(db.getWorkTree(), path + File.separatorChar
208 				+ Constants.DOT_GIT);
209 		if (!dotGit.getParentFile().exists())
210 			dotGit.getParentFile().mkdirs();
211 
212 		File modulesGitDir = new File(db.getDirectory(), "modules"
213 				+ File.separatorChar + path);
214 		try (BufferedWriter fw = Files.newBufferedWriter(dotGit.toPath(),
215 				UTF_8)) {
216 			fw.append("gitdir: " + "../" + Constants.DOT_GIT + "/modules/"
217 					+ path);
218 		}
219 		FileRepositoryBuilder builder = new FileRepositoryBuilder();
220 		builder.setWorkTree(new File(db.getWorkTree(), path));
221 		builder.build().create();
222 
223 		DirCache cache = db.lockDirCache();
224 		DirCacheEditor editor = cache.editor();
225 		editor.add(new PathEdit(path) {
226 
227 			@Override
228 			public void apply(DirCacheEntry ent) {
229 				ent.setFileMode(FileMode.GITLINK);
230 				ent.setObjectId(id);
231 			}
232 		});
233 		editor.commit();
234 
235 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
236 		assertTrue(gen.next());
237 		assertEquals(path, gen.getPath());
238 		assertEquals(id, gen.getObjectId());
239 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
240 		assertNull(gen.getConfigUpdate());
241 		assertNull(gen.getConfigUrl());
242 		assertNull(gen.getModulesPath());
243 		assertNull(gen.getModulesUpdate());
244 		assertNull(gen.getModulesUrl());
245 		try (Repository subRepo = gen.getRepository()) {
246 			assertNotNull(subRepo);
247 			assertEqualsFile(modulesGitDir, subRepo.getDirectory());
248 			assertEqualsFile(new File(db.getWorkTree(), path),
249 					subRepo.getWorkTree());
250 			subRepo.close();
251 			assertFalse(gen.next());
252 		}
253 	}
254 
255 	@Test
256 	public void repositoryWithNestedSubmodule() throws IOException,
257 			ConfigInvalidException {
258 		final ObjectId id = ObjectId
259 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
260 		final String path = "sub/dir/final";
261 		DirCache cache = db.lockDirCache();
262 		DirCacheEditor editor = cache.editor();
263 		editor.add(new PathEdit(path) {
264 
265 			@Override
266 			public void apply(DirCacheEntry ent) {
267 				ent.setFileMode(FileMode.GITLINK);
268 				ent.setObjectId(id);
269 			}
270 		});
271 		editor.commit();
272 
273 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
274 		assertTrue(gen.next());
275 		assertEquals(path, gen.getPath());
276 		assertEquals(id, gen.getObjectId());
277 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
278 		assertNull(gen.getConfigUpdate());
279 		assertNull(gen.getConfigUrl());
280 		assertNull(gen.getModulesPath());
281 		assertNull(gen.getModulesUpdate());
282 		assertNull(gen.getModulesUrl());
283 		assertNull(gen.getRepository());
284 		assertFalse(gen.next());
285 	}
286 
287 	@Test
288 	public void generatorFilteredToOneOfTwoSubmodules() throws IOException {
289 		final ObjectId id1 = ObjectId
290 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
291 		final String path1 = "sub1";
292 		final ObjectId id2 = ObjectId
293 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1235");
294 		final String path2 = "sub2";
295 		DirCache cache = db.lockDirCache();
296 		DirCacheEditor editor = cache.editor();
297 		editor.add(new PathEdit(path1) {
298 
299 			@Override
300 			public void apply(DirCacheEntry ent) {
301 				ent.setFileMode(FileMode.GITLINK);
302 				ent.setObjectId(id1);
303 			}
304 		});
305 		editor.add(new PathEdit(path2) {
306 
307 			@Override
308 			public void apply(DirCacheEntry ent) {
309 				ent.setFileMode(FileMode.GITLINK);
310 				ent.setObjectId(id2);
311 			}
312 		});
313 		editor.commit();
314 
315 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
316 		gen.setFilter(PathFilter.create(path1));
317 		assertTrue(gen.next());
318 		assertEquals(path1, gen.getPath());
319 		assertEquals(id1, gen.getObjectId());
320 		assertFalse(gen.next());
321 	}
322 
323 	@Test
324 	public void indexWithGitmodules() throws Exception {
325 		final ObjectId subId = ObjectId
326 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
327 		final String path = "sub";
328 
329 		final Config gitmodules = new Config();
330 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
331 				"sub");
332 		// Different config in the index should be overridden by the working tree.
333 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
334 				"git://example.com/bad");
335 		final RevBlob gitmodulesBlob = testDb.blob(gitmodules.toText());
336 
337 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
338 				"git://example.com/sub");
339 		writeTrashFile(DOT_GIT_MODULES, gitmodules.toText());
340 
341 		DirCache cache = db.lockDirCache();
342 		DirCacheEditor editor = cache.editor();
343 		editor.add(new PathEdit(path) {
344 
345 			@Override
346 			public void apply(DirCacheEntry ent) {
347 				ent.setFileMode(FileMode.GITLINK);
348 				ent.setObjectId(subId);
349 			}
350 		});
351 		editor.add(new PathEdit(DOT_GIT_MODULES) {
352 
353 			@Override
354 			public void apply(DirCacheEntry ent) {
355 				ent.setFileMode(FileMode.REGULAR_FILE);
356 				ent.setObjectId(gitmodulesBlob);
357 			}
358 		});
359 		editor.commit();
360 
361 		SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
362 		assertTrue(gen.next());
363 		assertEquals(path, gen.getPath());
364 		assertEquals(subId, gen.getObjectId());
365 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
366 		assertNull(gen.getConfigUpdate());
367 		assertNull(gen.getConfigUrl());
368 		assertEquals("sub", gen.getModulesPath());
369 		assertNull(gen.getModulesUpdate());
370 		assertEquals("git://example.com/sub", gen.getModulesUrl());
371 		assertNull(gen.getRepository());
372 		assertFalse(gen.next());
373 	}
374 
375 	@Test
376 	public void treeIdWithGitmodules() throws Exception {
377 		final ObjectId subId = ObjectId
378 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
379 		final String path = "sub";
380 
381 		final Config gitmodules = new Config();
382 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
383 				"sub");
384 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
385 				"git://example.com/sub");
386 
387 		RevCommit commit = testDb.getRevWalk().parseCommit(testDb.commit()
388 				.noParents()
389 				.add(DOT_GIT_MODULES, gitmodules.toText())
390 				.edit(new PathEdit(path) {
391 
392 							@Override
393 							public void apply(DirCacheEntry ent) {
394 								ent.setFileMode(FileMode.GITLINK);
395 								ent.setObjectId(subId);
396 							}
397 						})
398 				.create());
399 
400 		SubmoduleWalk gen = SubmoduleWalk.forPath(db, commit.getTree(), "sub");
401 		assertEquals(path, gen.getPath());
402 		assertEquals(subId, gen.getObjectId());
403 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
404 		assertNull(gen.getConfigUpdate());
405 		assertNull(gen.getConfigUrl());
406 		assertEquals("sub", gen.getModulesPath());
407 		assertNull(gen.getModulesUpdate());
408 		assertEquals("git://example.com/sub", gen.getModulesUrl());
409 		assertNull(gen.getRepository());
410 		assertFalse(gen.next());
411 	}
412 
413 	@Test
414 	public void testTreeIteratorWithGitmodules() throws Exception {
415 		final ObjectId subId = ObjectId
416 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
417 		final String path = "sub";
418 
419 		final Config gitmodules = new Config();
420 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
421 				"sub");
422 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
423 				"git://example.com/sub");
424 
425 		RevCommit commit = testDb.getRevWalk().parseCommit(testDb.commit()
426 				.noParents()
427 				.add(DOT_GIT_MODULES, gitmodules.toText())
428 				.edit(new PathEdit(path) {
429 
430 							@Override
431 							public void apply(DirCacheEntry ent) {
432 								ent.setFileMode(FileMode.GITLINK);
433 								ent.setObjectId(subId);
434 							}
435 						})
436 				.create());
437 
438 		final CanonicalTreeParser p = new CanonicalTreeParser();
439 		p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
440 		SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub");
441 		assertEquals(path, gen.getPath());
442 		assertEquals(subId, gen.getObjectId());
443 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
444 		assertNull(gen.getConfigUpdate());
445 		assertNull(gen.getConfigUrl());
446 		assertEquals("sub", gen.getModulesPath());
447 		assertNull(gen.getModulesUpdate());
448 		assertEquals("git://example.com/sub", gen.getModulesUrl());
449 		assertNull(gen.getRepository());
450 		assertFalse(gen.next());
451 	}
452 
453 	@Test
454 	public void testTreeIteratorWithGitmodulesNameNotPath() throws Exception {
455 		final ObjectId subId = ObjectId
456 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
457 		final String path = "sub";
458 		final String arbitraryName = "x";
459 
460 		final Config gitmodules = new Config();
461 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
462 				CONFIG_KEY_PATH, "sub");
463 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
464 				CONFIG_KEY_URL, "git://example.com/sub");
465 
466 		RevCommit commit = testDb.getRevWalk()
467 				.parseCommit(testDb.commit().noParents()
468 						.add(DOT_GIT_MODULES, gitmodules.toText())
469 						.edit(new PathEdit(path) {
470 
471 							@Override
472 							public void apply(DirCacheEntry ent) {
473 								ent.setFileMode(FileMode.GITLINK);
474 								ent.setObjectId(subId);
475 							}
476 						}).create());
477 
478 		final CanonicalTreeParser p = new CanonicalTreeParser();
479 		p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
480 		SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub");
481 		assertEquals(path, gen.getPath());
482 		assertEquals(subId, gen.getObjectId());
483 		assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
484 		assertNull(gen.getConfigUpdate());
485 		assertNull(gen.getConfigUrl());
486 		assertEquals("sub", gen.getModulesPath());
487 		assertNull(gen.getModulesUpdate());
488 		assertEquals("git://example.com/sub", gen.getModulesUrl());
489 		assertNull(gen.getRepository());
490 		assertFalse(gen.next());
491 	}
492 }