View Javadoc
1   /*
2    * Copyright (C) 2014, Christian Halstrick <christian.halstrick@sap.com> 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  
11  package org.eclipse.jgit.lib;
12  
13  import static org.junit.Assert.assertArrayEquals;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertFalse;
16  import static org.junit.Assert.assertTrue;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.Arrays;
21  import java.util.Set;
22  
23  import org.eclipse.jgit.api.CloneCommand;
24  import org.eclipse.jgit.api.Git;
25  import org.eclipse.jgit.api.errors.GitAPIException;
26  import org.eclipse.jgit.errors.NoWorkTreeException;
27  import org.eclipse.jgit.internal.storage.file.FileRepository;
28  import org.eclipse.jgit.junit.JGitTestUtil;
29  import org.eclipse.jgit.junit.RepositoryTestCase;
30  import org.eclipse.jgit.storage.file.FileBasedConfig;
31  import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
32  import org.eclipse.jgit.treewalk.FileTreeIterator;
33  import org.junit.Before;
34  import org.junit.Test;
35  import org.junit.experimental.theories.DataPoints;
36  import org.junit.experimental.theories.Theories;
37  import org.junit.experimental.theories.Theory;
38  import org.junit.runner.RunWith;
39  
40  @RunWith(Theories.class)
41  public class IndexDiffSubmoduleTest extends RepositoryTestCase {
42  	/** a submodule repository inside a root repository */
43  	protected FileRepository submodule_db;
44  
45  	/** Working directory of the submodule repository */
46  	protected File submodule_trash;
47  
48  	@DataPoints
49  	public static IgnoreSubmoduleMode allModes[] = IgnoreSubmoduleMode.values();
50  
51  	@Override
52  	@Before
53  	public void setUp() throws Exception {
54  		super.setUp();
55  		FileRepository submoduleStandalone = createWorkRepository();
56  		JGitTestUtil.writeTrashFile(submoduleStandalone, "fileInSubmodule",
57  				"submodule");
58  		Git submoduleStandaloneGit = Git.wrap(submoduleStandalone);
59  		submoduleStandaloneGit.add().addFilepattern("fileInSubmodule").call();
60  		submoduleStandaloneGit.commit().setMessage("add file to submodule")
61  				.call();
62  
63  		submodule_db = (FileRepository) Git.wrap(db).submoduleAdd()
64  				.setPath("modules/submodule")
65  				.setURI(submoduleStandalone.getDirectory().toURI().toString())
66  				.call();
67  		submodule_trash = submodule_db.getWorkTree();
68  		addRepoToClose(submodule_db);
69  		writeTrashFile("fileInRoot", "root");
70  		Git rootGit = Git.wrap(db);
71  		rootGit.add().addFilepattern("fileInRoot").call();
72  		rootGit.commit().setMessage("add submodule and root file").call();
73  	}
74  
75  	@Theory
76  	public void testInitiallyClean(IgnoreSubmoduleMode mode)
77  			throws IOException {
78  		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
79  				new FileTreeIterator(db));
80  		indexDiff.setIgnoreSubmoduleMode(mode);
81  		assertFalse(indexDiff.diff());
82  	}
83  
84  	private Repository cloneWithoutCloningSubmodule() throws Exception {
85  		File directory = createTempDirectory(
86  				"testCloneWithoutCloningSubmodules");
87  		CloneCommand clone = Git.cloneRepository();
88  		clone.setDirectory(directory);
89  		clone.setCloneSubmodules(false);
90  		clone.setURI(db.getDirectory().toURI().toString());
91  		Git git2 = clone.call();
92  		addRepoToClose(git2.getRepository());
93  		return git2.getRepository();
94  	}
95  
96  	@Theory
97  	public void testCleanAfterClone(IgnoreSubmoduleMode mode) throws Exception {
98  		Repository db2 = cloneWithoutCloningSubmodule();
99  		IndexDiff indexDiff = new IndexDiff(db2, Constants.HEAD,
100 				new FileTreeIterator(db2));
101 		indexDiff.setIgnoreSubmoduleMode(mode);
102 		boolean changed = indexDiff.diff();
103 		assertFalse(changed);
104 	}
105 
106 	@Theory
107 	public void testMissingIfDirectoryGone(IgnoreSubmoduleMode mode)
108 			throws Exception {
109 		recursiveDelete(submodule_trash);
110 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
111 				new FileTreeIterator(db));
112 		indexDiff.setIgnoreSubmoduleMode(mode);
113 		boolean hasChanges = indexDiff.diff();
114 		if (mode != IgnoreSubmoduleMode.ALL) {
115 			assertTrue(hasChanges);
116 			assertEquals("[modules/submodule]",
117 					indexDiff.getMissing().toString());
118 		} else {
119 			assertFalse(hasChanges);
120 		}
121 	}
122 
123 	@Theory
124 	public void testSubmoduleReplacedByFile(IgnoreSubmoduleMode mode)
125 			throws Exception {
126 		recursiveDelete(submodule_trash);
127 		writeTrashFile("modules/submodule", "nonsense");
128 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
129 				new FileTreeIterator(db));
130 		indexDiff.setIgnoreSubmoduleMode(mode);
131 		assertTrue(indexDiff.diff());
132 		assertEquals("[]", indexDiff.getMissing().toString());
133 		assertEquals("[]", indexDiff.getUntracked().toString());
134 		assertEquals("[modules/submodule]", indexDiff.getModified().toString());
135 	}
136 
137 	@Theory
138 	public void testDirtyRootWorktree(IgnoreSubmoduleMode mode)
139 			throws IOException {
140 		writeTrashFile("fileInRoot", "2");
141 
142 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
143 				new FileTreeIterator(db));
144 		indexDiff.setIgnoreSubmoduleMode(mode);
145 		assertTrue(indexDiff.diff());
146 	}
147 
148 	private void assertDiff(IndexDiff indexDiff, IgnoreSubmoduleMode mode,
149 			IgnoreSubmoduleMode... expectedEmptyModes) throws IOException {
150 		boolean diffResult = indexDiff.diff();
151 		Set<String> submodulePaths = indexDiff
152 				.getPathsWithIndexMode(FileMode.GITLINK);
153 		boolean emptyExpected = false;
154 		for (IgnoreSubmoduleMode empty : expectedEmptyModes) {
155 			if (mode.equals(empty)) {
156 				emptyExpected = true;
157 				break;
158 			}
159 		}
160 		if (emptyExpected) {
161 			assertFalse("diff should be false with mode=" + mode,
162 					diffResult);
163 			assertEquals("should have no paths with FileMode.GITLINK", 0,
164 					submodulePaths.size());
165 		} else {
166 			assertTrue("diff should be true with mode=" + mode,
167 					diffResult);
168 			assertTrue("submodule path should have FileMode.GITLINK",
169 					submodulePaths.contains("modules/submodule"));
170 		}
171 	}
172 
173 	@Theory
174 	public void testDirtySubmoduleWorktree(IgnoreSubmoduleMode mode)
175 			throws IOException {
176 		JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule", "2");
177 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
178 				new FileTreeIterator(db));
179 		indexDiff.setIgnoreSubmoduleMode(mode);
180 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
181 				IgnoreSubmoduleMode.DIRTY);
182 	}
183 
184 	@Theory
185 	public void testDirtySubmoduleHEAD(IgnoreSubmoduleMode mode)
186 			throws IOException, GitAPIException {
187 		JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule", "2");
188 		Git submoduleGit = Git.wrap(submodule_db);
189 		submoduleGit.add().addFilepattern("fileInSubmodule").call();
190 		submoduleGit.commit().setMessage("Modified fileInSubmodule").call();
191 
192 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
193 				new FileTreeIterator(db));
194 		indexDiff.setIgnoreSubmoduleMode(mode);
195 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL);
196 	}
197 
198 	@Theory
199 	public void testDirtySubmoduleIndex(IgnoreSubmoduleMode mode)
200 			throws IOException, GitAPIException {
201 		JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule", "2");
202 		Git submoduleGit = Git.wrap(submodule_db);
203 		submoduleGit.add().addFilepattern("fileInSubmodule").call();
204 
205 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
206 				new FileTreeIterator(db));
207 		indexDiff.setIgnoreSubmoduleMode(mode);
208 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
209 				IgnoreSubmoduleMode.DIRTY);
210 	}
211 
212 	@Theory
213 	public void testDirtySubmoduleIndexAndWorktree(IgnoreSubmoduleMode mode)
214 			throws IOException, GitAPIException, NoWorkTreeException {
215 		JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule", "2");
216 		Git submoduleGit = Git.wrap(submodule_db);
217 		submoduleGit.add().addFilepattern("fileInSubmodule").call();
218 		JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule", "3");
219 
220 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
221 				new FileTreeIterator(db));
222 		indexDiff.setIgnoreSubmoduleMode(mode);
223 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
224 				IgnoreSubmoduleMode.DIRTY);
225 	}
226 
227 	@Theory
228 	public void testDirtySubmoduleWorktreeUntracked(IgnoreSubmoduleMode mode)
229 			throws IOException {
230 		JGitTestUtil.writeTrashFile(submodule_db, "additionalFileInSubmodule",
231 				"2");
232 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
233 				new FileTreeIterator(db));
234 		indexDiff.setIgnoreSubmoduleMode(mode);
235 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
236 				IgnoreSubmoduleMode.DIRTY, IgnoreSubmoduleMode.UNTRACKED);
237 	}
238 
239 	@Theory
240 	public void testSubmoduleReplacedByMovedFile(IgnoreSubmoduleMode mode)
241 			throws Exception {
242 		Git git = Git.wrap(db);
243 		git.rm().setCached(true).addFilepattern("modules/submodule").call();
244 		recursiveDelete(submodule_trash);
245 		JGitTestUtil.deleteTrashFile(db, "fileInRoot");
246 		// Move the fileInRoot file
247 		writeTrashFile("modules/submodule/fileInRoot", "root");
248 		git.rm().addFilepattern("fileInRoot").addFilepattern("modules/").call();
249 		git.add().addFilepattern("modules/").call();
250 		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
251 				new FileTreeIterator(db));
252 		indexDiff.setIgnoreSubmoduleMode(mode);
253 		assertTrue(indexDiff.diff());
254 		String[] removed = indexDiff.getRemoved().toArray(new String[0]);
255 		Arrays.sort(removed);
256 		if (IgnoreSubmoduleMode.ALL.equals(mode)) {
257 			assertArrayEquals(new String[] { "fileInRoot" }, removed);
258 		} else {
259 			assertArrayEquals(
260 					new String[] { "fileInRoot", "modules/submodule" },
261 					removed);
262 		}
263 		assertEquals("[modules/submodule/fileInRoot]",
264 				indexDiff.getAdded().toString());
265 	}
266 
267 	@Test
268 	public void testIndexDiffTwoSubmodules() throws Exception {
269 		// Create a second submodule
270 		try (Repository submodule2 = createWorkRepository()) {
271 			JGitTestUtil.writeTrashFile(submodule2, "fileInSubmodule2",
272 					"submodule2");
273 			Git subGit = Git.wrap(submodule2);
274 			subGit.add().addFilepattern("fileInSubmodule2").call();
275 			subGit.commit().setMessage("add file to submodule2").call();
276 
277 			try (Repository sub2 = Git.wrap(db)
278 					.submoduleAdd().setPath("modules/submodule2")
279 					.setURI(submodule2.getDirectory().toURI().toString())
280 					.call()) {
281 				writeTrashFile("fileInRoot", "root+");
282 				Git rootGit = Git.wrap(db);
283 				rootGit.add().addFilepattern("fileInRoot").call();
284 				rootGit.commit().setMessage("add submodule2 and root file")
285 						.call();
286 				// Now change files in both submodules
287 				JGitTestUtil.writeTrashFile(submodule_db, "fileInSubmodule",
288 						"submodule changed");
289 				JGitTestUtil.writeTrashFile(sub2, "fileInSubmodule2",
290 						"submodule2 changed");
291 				// Set up .gitmodules
292 				FileBasedConfig gitmodules = new FileBasedConfig(
293 						new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
294 						db.getFS());
295 				gitmodules.load();
296 				gitmodules.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
297 						"modules/submodule", ConfigConstants.CONFIG_KEY_IGNORE,
298 						"all");
299 				gitmodules.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
300 						"modules/submodule2", ConfigConstants.CONFIG_KEY_IGNORE,
301 						"none");
302 				gitmodules.save();
303 				IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
304 						new FileTreeIterator(db));
305 				assertTrue(indexDiff.diff());
306 				String[] modified = indexDiff.getModified()
307 						.toArray(new String[0]);
308 				Arrays.sort(modified);
309 				assertEquals("[.gitmodules, modules/submodule2]",
310 						Arrays.toString(modified));
311 				// Try again with "dirty"
312 				gitmodules.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
313 						"modules/submodule", ConfigConstants.CONFIG_KEY_IGNORE,
314 						"dirty");
315 				gitmodules.save();
316 				indexDiff = new IndexDiff(db, Constants.HEAD,
317 						new FileTreeIterator(db));
318 				assertTrue(indexDiff.diff());
319 				modified = indexDiff.getModified().toArray(new String[0]);
320 				Arrays.sort(modified);
321 				assertEquals("[.gitmodules, modules/submodule2]",
322 						Arrays.toString(modified));
323 				// Test the config override
324 				StoredConfig cfg = db.getConfig();
325 				cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
326 						"modules/submodule", ConfigConstants.CONFIG_KEY_IGNORE,
327 						"none");
328 				cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
329 						"modules/submodule2", ConfigConstants.CONFIG_KEY_IGNORE,
330 						"all");
331 				cfg.save();
332 				indexDiff = new IndexDiff(db, Constants.HEAD,
333 						new FileTreeIterator(db));
334 				assertTrue(indexDiff.diff());
335 				modified = indexDiff.getModified().toArray(new String[0]);
336 				Arrays.sort(modified);
337 				assertEquals("[.gitmodules, modules/submodule]",
338 						Arrays.toString(modified));
339 			}
340 		}
341 	}
342 }