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