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