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 				assertFalse(gen.next());
221 			}
222 		}
223 	}
224 
225 	@Test
226 	public void repositoryWithNestedSubmodule() throws IOException,
227 			ConfigInvalidException {
228 		final ObjectId id = ObjectId
229 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
230 		final String path = "sub/dir/final";
231 		DirCache cache = db.lockDirCache();
232 		DirCacheEditor editor = cache.editor();
233 		editor.add(new PathEdit(path) {
234 
235 			@Override
236 			public void apply(DirCacheEntry ent) {
237 				ent.setFileMode(FileMode.GITLINK);
238 				ent.setObjectId(id);
239 			}
240 		});
241 		editor.commit();
242 
243 		try (SubmoduleWalk gen = SubmoduleWalk.forIndex(db)) {
244 			assertTrue(gen.next());
245 			assertEquals(path, gen.getPath());
246 			assertEquals(id, gen.getObjectId());
247 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
248 			assertNull(gen.getConfigUpdate());
249 			assertNull(gen.getConfigUrl());
250 			assertNull(gen.getModulesPath());
251 			assertNull(gen.getModulesUpdate());
252 			assertNull(gen.getModulesUrl());
253 			assertNull(gen.getRepository());
254 			assertFalse(gen.next());
255 		}
256 	}
257 
258 	@Test
259 	public void generatorFilteredToOneOfTwoSubmodules() throws IOException {
260 		final ObjectId id1 = ObjectId
261 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
262 		final String path1 = "sub1";
263 		final ObjectId id2 = ObjectId
264 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1235");
265 		final String path2 = "sub2";
266 		DirCache cache = db.lockDirCache();
267 		DirCacheEditor editor = cache.editor();
268 		editor.add(new PathEdit(path1) {
269 
270 			@Override
271 			public void apply(DirCacheEntry ent) {
272 				ent.setFileMode(FileMode.GITLINK);
273 				ent.setObjectId(id1);
274 			}
275 		});
276 		editor.add(new PathEdit(path2) {
277 
278 			@Override
279 			public void apply(DirCacheEntry ent) {
280 				ent.setFileMode(FileMode.GITLINK);
281 				ent.setObjectId(id2);
282 			}
283 		});
284 		editor.commit();
285 
286 		try (SubmoduleWalk gen = SubmoduleWalk.forIndex(db)) {
287 			gen.setFilter(PathFilter.create(path1));
288 			assertTrue(gen.next());
289 			assertEquals(path1, gen.getPath());
290 			assertEquals(id1, gen.getObjectId());
291 			assertFalse(gen.next());
292 		}
293 	}
294 
295 	@Test
296 	public void indexWithGitmodules() throws Exception {
297 		final ObjectId subId = ObjectId
298 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
299 		final String path = "sub";
300 
301 		final Config gitmodules = new Config();
302 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
303 				"sub");
304 		// Different config in the index should be overridden by the working tree.
305 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
306 				"git://example.com/bad");
307 		final RevBlob gitmodulesBlob = testDb.blob(gitmodules.toText());
308 
309 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
310 				"git://example.com/sub");
311 		writeTrashFile(DOT_GIT_MODULES, gitmodules.toText());
312 
313 		DirCache cache = db.lockDirCache();
314 		DirCacheEditor editor = cache.editor();
315 		editor.add(new PathEdit(path) {
316 
317 			@Override
318 			public void apply(DirCacheEntry ent) {
319 				ent.setFileMode(FileMode.GITLINK);
320 				ent.setObjectId(subId);
321 			}
322 		});
323 		editor.add(new PathEdit(DOT_GIT_MODULES) {
324 
325 			@Override
326 			public void apply(DirCacheEntry ent) {
327 				ent.setFileMode(FileMode.REGULAR_FILE);
328 				ent.setObjectId(gitmodulesBlob);
329 			}
330 		});
331 		editor.commit();
332 
333 		try (SubmoduleWalk gen = SubmoduleWalk.forIndex(db)) {
334 			assertTrue(gen.next());
335 			assertEquals(path, gen.getPath());
336 			assertEquals(subId, gen.getObjectId());
337 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
338 			assertNull(gen.getConfigUpdate());
339 			assertNull(gen.getConfigUrl());
340 			assertEquals("sub", gen.getModulesPath());
341 			assertNull(gen.getModulesUpdate());
342 			assertEquals("git://example.com/sub", gen.getModulesUrl());
343 			assertNull(gen.getRepository());
344 			assertFalse(gen.next());
345 		}
346 	}
347 
348 	@Test
349 	public void treeIdWithGitmodules() throws Exception {
350 		final ObjectId subId = ObjectId
351 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
352 		final String path = "sub";
353 
354 		final Config gitmodules = new Config();
355 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
356 				"sub");
357 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
358 				"git://example.com/sub");
359 
360 		RevCommit commit = testDb.getRevWalk().parseCommit(testDb.commit()
361 				.noParents()
362 				.add(DOT_GIT_MODULES, gitmodules.toText())
363 				.edit(new PathEdit(path) {
364 
365 							@Override
366 							public void apply(DirCacheEntry ent) {
367 								ent.setFileMode(FileMode.GITLINK);
368 								ent.setObjectId(subId);
369 							}
370 						})
371 				.create());
372 
373 		try (SubmoduleWalk gen = SubmoduleWalk.forPath(db, commit.getTree(),
374 				"sub")) {
375 			assertEquals(path, gen.getPath());
376 			assertEquals(subId, gen.getObjectId());
377 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
378 			assertNull(gen.getConfigUpdate());
379 			assertNull(gen.getConfigUrl());
380 			assertEquals("sub", gen.getModulesPath());
381 			assertNull(gen.getModulesUpdate());
382 			assertEquals("git://example.com/sub", gen.getModulesUrl());
383 			assertNull(gen.getRepository());
384 			assertFalse(gen.next());
385 		}
386 	}
387 
388 	@Test
389 	public void testTreeIteratorWithGitmodules() throws Exception {
390 		final ObjectId subId = ObjectId
391 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
392 		final String path = "sub";
393 
394 		final Config gitmodules = new Config();
395 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_PATH,
396 				"sub");
397 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, path, CONFIG_KEY_URL,
398 				"git://example.com/sub");
399 
400 		RevCommit commit = testDb.getRevWalk().parseCommit(testDb.commit()
401 				.noParents()
402 				.add(DOT_GIT_MODULES, gitmodules.toText())
403 				.edit(new PathEdit(path) {
404 
405 							@Override
406 							public void apply(DirCacheEntry ent) {
407 								ent.setFileMode(FileMode.GITLINK);
408 								ent.setObjectId(subId);
409 							}
410 						})
411 				.create());
412 
413 		final CanonicalTreeParser p = new CanonicalTreeParser();
414 		p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
415 		try (SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub")) {
416 			assertEquals(path, gen.getPath());
417 			assertEquals(subId, gen.getObjectId());
418 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
419 			assertNull(gen.getConfigUpdate());
420 			assertNull(gen.getConfigUrl());
421 			assertEquals("sub", gen.getModulesPath());
422 			assertNull(gen.getModulesUpdate());
423 			assertEquals("git://example.com/sub", gen.getModulesUrl());
424 			assertNull(gen.getRepository());
425 			assertFalse(gen.next());
426 		}
427 	}
428 
429 	@Test
430 	public void testTreeIteratorWithGitmodulesNameNotPath() throws Exception {
431 		final ObjectId subId = ObjectId
432 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
433 		final String path = "sub";
434 		final String arbitraryName = "x";
435 
436 		final Config gitmodules = new Config();
437 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
438 				CONFIG_KEY_PATH, "sub");
439 		gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
440 				CONFIG_KEY_URL, "git://example.com/sub");
441 
442 		RevCommit commit = testDb.getRevWalk()
443 				.parseCommit(testDb.commit().noParents()
444 						.add(DOT_GIT_MODULES, gitmodules.toText())
445 						.edit(new PathEdit(path) {
446 
447 							@Override
448 							public void apply(DirCacheEntry ent) {
449 								ent.setFileMode(FileMode.GITLINK);
450 								ent.setObjectId(subId);
451 							}
452 						}).create());
453 
454 		final CanonicalTreeParser p = new CanonicalTreeParser();
455 		p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
456 		try (SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub")) {
457 			assertEquals(arbitraryName, gen.getModuleName());
458 			assertEquals(path, gen.getPath());
459 			assertEquals(subId, gen.getObjectId());
460 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
461 			assertNull(gen.getConfigUpdate());
462 			assertNull(gen.getConfigUrl());
463 			assertEquals("sub", gen.getModulesPath());
464 			assertNull(gen.getModulesUpdate());
465 			assertEquals("git://example.com/sub", gen.getModulesUrl());
466 			assertNull(gen.getRepository());
467 			assertFalse(gen.next());
468 		}
469 	}
470 }