View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5    * Copyright (C) 2013, Robin Stocker <robin@nibor.org> and others
6    *
7    * This program and the accompanying materials are made available under the
8    * terms of the Eclipse Distribution License v. 1.0 which is available at
9    * https://www.eclipse.org/org/documents/edl-v10.php.
10   *
11   * SPDX-License-Identifier: BSD-3-Clause
12   */
13  
14  package org.eclipse.jgit.lib;
15  
16  import static org.junit.Assert.assertEquals;
17  import static org.junit.Assert.assertFalse;
18  import static org.junit.Assert.assertTrue;
19  
20  import java.io.File;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.TreeSet;
27  
28  import org.eclipse.jgit.api.Git;
29  import org.eclipse.jgit.api.MergeResult;
30  import org.eclipse.jgit.api.MergeResult.MergeStatus;
31  import org.eclipse.jgit.api.errors.GitAPIException;
32  import org.eclipse.jgit.dircache.DirCache;
33  import org.eclipse.jgit.dircache.DirCacheBuilder;
34  import org.eclipse.jgit.dircache.DirCacheEditor;
35  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
36  import org.eclipse.jgit.dircache.DirCacheEntry;
37  import org.eclipse.jgit.junit.RepositoryTestCase;
38  import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
39  import org.eclipse.jgit.lib.IndexDiff.StageState;
40  import org.eclipse.jgit.merge.MergeStrategy;
41  import org.eclipse.jgit.revwalk.RevCommit;
42  import org.eclipse.jgit.storage.file.FileBasedConfig;
43  import org.eclipse.jgit.treewalk.FileTreeIterator;
44  import org.eclipse.jgit.util.IO;
45  import org.junit.Test;
46  
47  public class IndexDiffTest extends RepositoryTestCase {
48  
49  	static PathEdit add(final Repository db, final File workdir,
50  			final String path) throws FileNotFoundException, IOException {
51  		final File f = new File(workdir, path);
52  		ObjectId id;
53  		try (ObjectInserter inserter = db.newObjectInserter()) {
54  			id = inserter.insert(Constants.OBJ_BLOB,
55  				IO.readFully(f));
56  		}
57  		return new PathEdit(path) {
58  			@Override
59  			public void apply(DirCacheEntry ent) {
60  				ent.setFileMode(FileMode.REGULAR_FILE);
61  				ent.setLength(f.length());
62  				ent.setObjectId(id);
63  			}
64  		};
65  	}
66  
67  	@Test
68  	public void testAdded() throws IOException {
69  		writeTrashFile("file1", "file1");
70  		writeTrashFile("dir/subfile", "dir/subfile");
71  		ObjectId tree = insertTree(new TreeFormatter());
72  
73  		DirCache index = db.lockDirCache();
74  		DirCacheEditor editor = index.editor();
75  		editor.add(add(db, trash, "file1"));
76  		editor.add(add(db, trash, "dir/subfile"));
77  		editor.commit();
78  		FileTreeIterator iterator = new FileTreeIterator(db);
79  		IndexDiff diff = new IndexDiff(db, tree, iterator);
80  		diff.diff();
81  		assertEquals(2, diff.getAdded().size());
82  		assertTrue(diff.getAdded().contains("file1"));
83  		assertTrue(diff.getAdded().contains("dir/subfile"));
84  		assertEquals(0, diff.getChanged().size());
85  		assertEquals(0, diff.getModified().size());
86  		assertEquals(0, diff.getRemoved().size());
87  		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
88  	}
89  
90  	@Test
91  	public void testMissing() throws Exception {
92  		File file2 = writeTrashFile("file2", "file2");
93  		File file3 = writeTrashFile("dir/file3", "dir/file3");
94  		try (Git git = new Git(db)) {
95  			git.add().addFilepattern("file2").addFilepattern("dir/file3")
96  					.call();
97  			git.commit().setMessage("commit").call();
98  		}
99  		assertTrue(file2.delete());
100 		assertTrue(file3.delete());
101 		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
102 				new FileTreeIterator(db));
103 		diff.diff();
104 		assertEquals(2, diff.getMissing().size());
105 		assertTrue(diff.getMissing().contains("file2"));
106 		assertTrue(diff.getMissing().contains("dir/file3"));
107 		assertEquals(0, diff.getChanged().size());
108 		assertEquals(0, diff.getModified().size());
109 		assertEquals(0, diff.getAdded().size());
110 		assertEquals(0, diff.getRemoved().size());
111 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
112 	}
113 
114 	@Test
115 	public void testRemoved() throws IOException {
116 		writeTrashFile("file2", "file2");
117 		writeTrashFile("dir/file3", "dir/file3");
118 
119 		TreeFormatter dir = new TreeFormatter();
120 		dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
121 
122 		TreeFormatter tree = new TreeFormatter();
123 		tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
124 		tree.append("dir", FileMode.TREE, insertTree(dir));
125 		ObjectId treeId = insertTree(tree);
126 
127 		FileTreeIterator iterator = new FileTreeIterator(db);
128 		IndexDiff diff = new IndexDiff(db, treeId, iterator);
129 		diff.diff();
130 		assertEquals(2, diff.getRemoved().size());
131 		assertTrue(diff.getRemoved().contains("file2"));
132 		assertTrue(diff.getRemoved().contains("dir/file3"));
133 		assertEquals(0, diff.getChanged().size());
134 		assertEquals(0, diff.getModified().size());
135 		assertEquals(0, diff.getAdded().size());
136 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
137 	}
138 
139 	@Test
140 	public void testModified() throws IOException, GitAPIException {
141 
142 		writeTrashFile("file2", "file2");
143 		writeTrashFile("dir/file3", "dir/file3");
144 
145 		try (Git git = new Git(db)) {
146 			git.add().addFilepattern("file2").addFilepattern("dir/file3").call();
147 		}
148 
149 		writeTrashFile("dir/file3", "changed");
150 
151 		TreeFormatter dir = new TreeFormatter();
152 		dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
153 
154 		TreeFormatter tree = new TreeFormatter();
155 		tree.append("dir", FileMode.TREE, insertTree(dir));
156 		tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
157 		ObjectId treeId = insertTree(tree);
158 
159 		FileTreeIterator iterator = new FileTreeIterator(db);
160 		IndexDiff diff = new IndexDiff(db, treeId, iterator);
161 		diff.diff();
162 		assertEquals(2, diff.getChanged().size());
163 		assertTrue(diff.getChanged().contains("file2"));
164 		assertTrue(diff.getChanged().contains("dir/file3"));
165 		assertEquals(1, diff.getModified().size());
166 		assertTrue(diff.getModified().contains("dir/file3"));
167 		assertEquals(0, diff.getAdded().size());
168 		assertEquals(0, diff.getRemoved().size());
169 		assertEquals(0, diff.getMissing().size());
170 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
171 	}
172 
173 	@Test
174 	public void testConflicting() throws Exception {
175 		try (Git git = new Git(db)) {
176 			writeTrashFile("a", "1\na\n3\n");
177 			writeTrashFile("b", "1\nb\n3\n");
178 			git.add().addFilepattern("a").addFilepattern("b").call();
179 			RevCommit initialCommit = git.commit().setMessage("initial").call();
180 
181 			// create side branch with two modifications
182 			createBranch(initialCommit, "refs/heads/side");
183 			checkoutBranch("refs/heads/side");
184 			writeTrashFile("a", "1\na(side)\n3\n");
185 			writeTrashFile("b", "1\nb\n3\n(side)");
186 			git.add().addFilepattern("a").addFilepattern("b").call();
187 			RevCommit secondCommit = git.commit().setMessage("side").call();
188 
189 			// update a on master to generate conflict
190 			checkoutBranch("refs/heads/master");
191 			writeTrashFile("a", "1\na(main)\n3\n");
192 			git.add().addFilepattern("a").call();
193 			git.commit().setMessage("main").call();
194 
195 			// merge side with master
196 			MergeResult result = git.merge().include(secondCommit.getId())
197 					.setStrategy(MergeStrategy.RESOLVE).call();
198 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
199 		}
200 
201 		FileTreeIterator iterator = new FileTreeIterator(db);
202 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
203 		diff.diff();
204 
205 		assertEquals("[b]",
206 				new TreeSet<>(diff.getChanged()).toString());
207 		assertEquals("[]", diff.getAdded().toString());
208 		assertEquals("[]", diff.getRemoved().toString());
209 		assertEquals("[]", diff.getMissing().toString());
210 		assertEquals("[]", diff.getModified().toString());
211 		assertEquals("[a]", diff.getConflicting().toString());
212 		assertEquals(StageState.BOTH_MODIFIED,
213 				diff.getConflictingStageStates().get("a"));
214 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
215 	}
216 
217 	@Test
218 	public void testConflictingDeletedAndModified() throws Exception {
219 		try (Git git = new Git(db)) {
220 			writeTrashFile("a", "1\na\n3\n");
221 			writeTrashFile("b", "1\nb\n3\n");
222 			git.add().addFilepattern("a").addFilepattern("b").call();
223 			RevCommit initialCommit = git.commit().setMessage("initial").call();
224 
225 			// create side branch and delete "a"
226 			createBranch(initialCommit, "refs/heads/side");
227 			checkoutBranch("refs/heads/side");
228 			git.rm().addFilepattern("a").call();
229 			RevCommit secondCommit = git.commit().setMessage("side").call();
230 
231 			// update a on master to generate conflict
232 			checkoutBranch("refs/heads/master");
233 			writeTrashFile("a", "1\na(main)\n3\n");
234 			git.add().addFilepattern("a").call();
235 			git.commit().setMessage("main").call();
236 
237 			// merge side with master
238 			MergeResult result = git.merge().include(secondCommit.getId())
239 					.setStrategy(MergeStrategy.RESOLVE).call();
240 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
241 		}
242 
243 		FileTreeIterator iterator = new FileTreeIterator(db);
244 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
245 		diff.diff();
246 
247 		assertEquals("[]", new TreeSet<>(diff.getChanged()).toString());
248 		assertEquals("[]", diff.getAdded().toString());
249 		assertEquals("[]", diff.getRemoved().toString());
250 		assertEquals("[]", diff.getMissing().toString());
251 		assertEquals("[]", diff.getModified().toString());
252 		assertEquals("[a]", diff.getConflicting().toString());
253 		assertEquals(StageState.DELETED_BY_THEM,
254 				diff.getConflictingStageStates().get("a"));
255 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
256 	}
257 
258 	@Test
259 	public void testConflictingFromMultipleCreations() throws Exception {
260 		try (Git git = new Git(db)) {
261 			writeTrashFile("a", "1\na\n3\n");
262 			git.add().addFilepattern("a").call();
263 			RevCommit initialCommit = git.commit().setMessage("initial").call();
264 
265 			createBranch(initialCommit, "refs/heads/side");
266 			checkoutBranch("refs/heads/side");
267 
268 			writeTrashFile("b", "1\nb(side)\n3\n");
269 			git.add().addFilepattern("b").call();
270 			RevCommit secondCommit = git.commit().setMessage("side").call();
271 
272 			checkoutBranch("refs/heads/master");
273 
274 			writeTrashFile("b", "1\nb(main)\n3\n");
275 			git.add().addFilepattern("b").call();
276 			git.commit().setMessage("main").call();
277 
278 			MergeResult result = git.merge().include(secondCommit.getId())
279 					.setStrategy(MergeStrategy.RESOLVE).call();
280 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
281 		}
282 
283 		FileTreeIterator iterator = new FileTreeIterator(db);
284 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
285 		diff.diff();
286 
287 		assertEquals("[]", new TreeSet<>(diff.getChanged()).toString());
288 		assertEquals("[]", diff.getAdded().toString());
289 		assertEquals("[]", diff.getRemoved().toString());
290 		assertEquals("[]", diff.getMissing().toString());
291 		assertEquals("[]", diff.getModified().toString());
292 		assertEquals("[b]", diff.getConflicting().toString());
293 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
294 	}
295 
296 	@Test
297 	public void testUnchangedSimple() throws IOException, GitAPIException {
298 		writeTrashFile("a.b", "a.b");
299 		writeTrashFile("a.c", "a.c");
300 		writeTrashFile("a=c", "a=c");
301 		writeTrashFile("a=d", "a=d");
302 		try (Git git = new Git(db)) {
303 			git.add().addFilepattern("a.b").call();
304 			git.add().addFilepattern("a.c").call();
305 			git.add().addFilepattern("a=c").call();
306 			git.add().addFilepattern("a=d").call();
307 		}
308 
309 		TreeFormatter tree = new TreeFormatter();
310 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
311 		tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
312 		tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
313 		tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
314 		tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
315 		ObjectId treeId = insertTree(tree);
316 
317 		FileTreeIterator iterator = new FileTreeIterator(db);
318 		IndexDiff diff = new IndexDiff(db, treeId, iterator);
319 		diff.diff();
320 		assertEquals(0, diff.getChanged().size());
321 		assertEquals(0, diff.getAdded().size());
322 		assertEquals(0, diff.getRemoved().size());
323 		assertEquals(0, diff.getMissing().size());
324 		assertEquals(0, diff.getModified().size());
325 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
326 	}
327 
328 	/**
329 	 * This test has both files and directories that involve the tricky ordering
330 	 * used by Git.
331 	 *
332 	 * @throws IOException
333 	 * @throws GitAPIException
334 	 */
335 	@Test
336 	public void testUnchangedComplex() throws IOException, GitAPIException {
337 		writeTrashFile("a.b", "a.b");
338 		writeTrashFile("a.c", "a.c");
339 		writeTrashFile("a/b.b/b", "a/b.b/b");
340 		writeTrashFile("a/b", "a/b");
341 		writeTrashFile("a/c", "a/c");
342 		writeTrashFile("a=c", "a=c");
343 		writeTrashFile("a=d", "a=d");
344 		try (Git git = new Git(db)) {
345 			git.add().addFilepattern("a.b").addFilepattern("a.c")
346 					.addFilepattern("a/b.b/b").addFilepattern("a/b")
347 					.addFilepattern("a/c").addFilepattern("a=c")
348 					.addFilepattern("a=d").call();
349 		}
350 
351 
352 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
353 		TreeFormatter bb = new TreeFormatter();
354 		bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
355 
356 		TreeFormatter a = new TreeFormatter();
357 		a.append("b", FileMode.REGULAR_FILE, ObjectId
358 				.fromString("db89c972fc57862eae378f45b74aca228037d415"));
359 		a.append("b.b", FileMode.TREE, insertTree(bb));
360 		a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
361 
362 		TreeFormatter tree = new TreeFormatter();
363 		tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
364 		tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
365 		tree.append("a", FileMode.TREE, insertTree(a));
366 		tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
367 		tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
368 		ObjectId treeId = insertTree(tree);
369 
370 		FileTreeIterator iterator = new FileTreeIterator(db);
371 		IndexDiff diff = new IndexDiff(db, treeId, iterator);
372 		diff.diff();
373 		assertEquals(0, diff.getChanged().size());
374 		assertEquals(0, diff.getAdded().size());
375 		assertEquals(0, diff.getRemoved().size());
376 		assertEquals(0, diff.getMissing().size());
377 		assertEquals(0, diff.getModified().size());
378 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
379 	}
380 
381 	private ObjectId insertTree(TreeFormatter tree) throws IOException {
382 		try (ObjectInserter oi = db.newObjectInserter()) {
383 			ObjectId id = oi.insert(tree);
384 			oi.flush();
385 			return id;
386 		}
387 	}
388 
389 	/**
390 	 * A file is removed from the index but stays in the working directory. It
391 	 * is checked if IndexDiff detects this file as removed and untracked.
392 	 *
393 	 * @throws Exception
394 	 */
395 	@Test
396 	public void testRemovedUntracked() throws Exception{
397 		String path = "file";
398 		try (Git git = new Git(db)) {
399 			writeTrashFile(path, "content");
400 			git.add().addFilepattern(path).call();
401 			git.commit().setMessage("commit").call();
402 		}
403 		removeFromIndex(path);
404 		FileTreeIterator iterator = new FileTreeIterator(db);
405 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
406 		diff.diff();
407 		assertTrue(diff.getRemoved().contains(path));
408 		assertTrue(diff.getUntracked().contains(path));
409 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
410 	}
411 
412 	/**
413 	 *
414 	 * @throws Exception
415 	 */
416 	@Test
417 	public void testUntrackedFolders() throws Exception {
418 		try (Git git = new Git(db)) {
419 			IndexDiff diff = new IndexDiff(db, Constants.HEAD,
420 					new FileTreeIterator(db));
421 			diff.diff();
422 			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
423 
424 			writeTrashFile("readme", "");
425 			writeTrashFile("src/com/A.java", "");
426 			writeTrashFile("src/com/B.java", "");
427 			writeTrashFile("src/org/A.java", "");
428 			writeTrashFile("src/org/B.java", "");
429 			writeTrashFile("target/com/A.java", "");
430 			writeTrashFile("target/com/B.java", "");
431 			writeTrashFile("target/org/A.java", "");
432 			writeTrashFile("target/org/B.java", "");
433 
434 			git.add().addFilepattern("src").addFilepattern("readme").call();
435 			git.commit().setMessage("initial").call();
436 
437 			diff = new IndexDiff(db, Constants.HEAD,
438 					new FileTreeIterator(db));
439 			diff.diff();
440 			assertEquals(new HashSet<>(Arrays.asList("target")),
441 					diff.getUntrackedFolders());
442 
443 			writeTrashFile("src/tst/A.java", "");
444 			writeTrashFile("src/tst/B.java", "");
445 
446 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
447 			diff.diff();
448 			assertEquals(new HashSet<>(Arrays.asList("target", "src/tst")),
449 					diff.getUntrackedFolders());
450 
451 			git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org")
452 					.call();
453 			git.commit().setMessage("second").call();
454 			writeTrashFile("src/org/C.java", "");
455 
456 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
457 			diff.diff();
458 			assertEquals(
459 					new HashSet<>(Arrays.asList("src/org", "src/tst",
460 							"target")),
461 					diff.getUntrackedFolders());
462 		}
463 	}
464 
465 	/**
466 	 * Test that ignored folders aren't listed as untracked, but are listed as
467 	 * ignored.
468 	 *
469 	 * @throws Exception
470 	 */
471 	@Test
472 	public void testUntrackedNotIgnoredFolders() throws Exception {
473 		try (Git git = new Git(db)) {
474 			IndexDiff diff = new IndexDiff(db, Constants.HEAD,
475 					new FileTreeIterator(db));
476 			diff.diff();
477 			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
478 
479 			writeTrashFile("readme", "");
480 			writeTrashFile("sr/com/X.java", "");
481 			writeTrashFile("src/com/A.java", "");
482 			writeTrashFile("src/org/B.java", "");
483 			writeTrashFile("srcs/org/Y.java", "");
484 			writeTrashFile("target/com/A.java", "");
485 			writeTrashFile("target/org/B.java", "");
486 			writeTrashFile(".gitignore", "/target\n/sr");
487 
488 			git.add().addFilepattern("readme").addFilepattern(".gitignore")
489 					.addFilepattern("srcs/").call();
490 			git.commit().setMessage("initial").call();
491 
492 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
493 			diff.diff();
494 			assertEquals(new HashSet<>(Arrays.asList("src")),
495 					diff.getUntrackedFolders());
496 			assertEquals(new HashSet<>(Arrays.asList("sr", "target")),
497 					diff.getIgnoredNotInIndex());
498 
499 			git.add().addFilepattern("src").call();
500 			writeTrashFile("sr/com/X1.java", "");
501 			writeTrashFile("src/tst/A.java", "");
502 			writeTrashFile("src/tst/B.java", "");
503 			writeTrashFile("srcs/com/Y1.java", "");
504 			deleteTrashFile(".gitignore");
505 
506 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
507 			diff.diff();
508 			assertEquals(
509 					new HashSet<>(Arrays.asList("srcs/com", "sr", "src/tst",
510 							"target")),
511 					diff.getUntrackedFolders());
512 		}
513 	}
514 
515 	@Test
516 	public void testAssumeUnchanged() throws Exception {
517 		try (Git git = new Git(db)) {
518 			String path = "file";
519 			writeTrashFile(path, "content");
520 			git.add().addFilepattern(path).call();
521 			String path2 = "file2";
522 			writeTrashFile(path2, "content");
523 			String path3 = "file3";
524 			writeTrashFile(path3, "some content");
525 			git.add().addFilepattern(path2).addFilepattern(path3).call();
526 			git.commit().setMessage("commit").call();
527 			assumeUnchanged(path2);
528 			assumeUnchanged(path3);
529 			writeTrashFile(path, "more content");
530 			deleteTrashFile(path3);
531 
532 			FileTreeIterator iterator = new FileTreeIterator(db);
533 			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
534 			diff.diff();
535 			assertEquals(2, diff.getAssumeUnchanged().size());
536 			assertEquals(1, diff.getModified().size());
537 			assertEquals(0, diff.getChanged().size());
538 			assertTrue(diff.getAssumeUnchanged().contains("file2"));
539 			assertTrue(diff.getAssumeUnchanged().contains("file3"));
540 			assertTrue(diff.getModified().contains("file"));
541 
542 			git.add().addFilepattern(".").call();
543 
544 			iterator = new FileTreeIterator(db);
545 			diff = new IndexDiff(db, Constants.HEAD, iterator);
546 			diff.diff();
547 			assertEquals(2, diff.getAssumeUnchanged().size());
548 			assertEquals(0, diff.getModified().size());
549 			assertEquals(1, diff.getChanged().size());
550 			assertTrue(diff.getAssumeUnchanged().contains("file2"));
551 			assertTrue(diff.getAssumeUnchanged().contains("file3"));
552 			assertTrue(diff.getChanged().contains("file"));
553 			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
554 		}
555 	}
556 
557 	@Test
558 	public void testStageState() throws IOException {
559 		final int base = DirCacheEntry.STAGE_1;
560 		final int ours = DirCacheEntry.STAGE_2;
561 		final int theirs = DirCacheEntry.STAGE_3;
562 		verifyStageState(StageState.BOTH_DELETED, base);
563 		verifyStageState(StageState.DELETED_BY_THEM, ours, base);
564 		verifyStageState(StageState.DELETED_BY_US, base, theirs);
565 		verifyStageState(StageState.BOTH_MODIFIED, base, ours, theirs);
566 		verifyStageState(StageState.ADDED_BY_US, ours);
567 		verifyStageState(StageState.BOTH_ADDED, ours, theirs);
568 		verifyStageState(StageState.ADDED_BY_THEM, theirs);
569 
570 		assertTrue(StageState.BOTH_DELETED.hasBase());
571 		assertFalse(StageState.BOTH_DELETED.hasOurs());
572 		assertFalse(StageState.BOTH_DELETED.hasTheirs());
573 		assertFalse(StageState.BOTH_ADDED.hasBase());
574 		assertTrue(StageState.BOTH_ADDED.hasOurs());
575 		assertTrue(StageState.BOTH_ADDED.hasTheirs());
576 	}
577 
578 	@Test
579 	public void testStageState_mergeAndReset_bug() throws Exception {
580 		try (Git git = new Git(db)) {
581 			writeTrashFile("a", "content");
582 			git.add().addFilepattern("a").call();
583 			RevCommit initialCommit = git.commit().setMessage("initial commit")
584 					.call();
585 
586 			// create branch and add a new file
587 			final String branchName = Constants.R_HEADS + "branch";
588 			createBranch(initialCommit, branchName);
589 			checkoutBranch(branchName);
590 			writeTrashFile("b", "second file content - branch");
591 			git.add().addFilepattern("b").call();
592 			RevCommit branchCommit = git.commit().setMessage("branch commit")
593 					.call();
594 
595 			// checkout master and add the same new file
596 			checkoutBranch(Constants.R_HEADS + Constants.MASTER);
597 			writeTrashFile("b", "second file content - master");
598 			git.add().addFilepattern("b").call();
599 			git.commit().setMessage("master commit").call();
600 
601 			// try and merge
602 			MergeResult result = git.merge().include(branchCommit).call();
603 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
604 
605 			FileTreeIterator iterator = new FileTreeIterator(db);
606 			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
607 			diff.diff();
608 
609 			assertTrue(diff.getChanged().isEmpty());
610 			assertTrue(diff.getAdded().isEmpty());
611 			assertTrue(diff.getRemoved().isEmpty());
612 			assertTrue(diff.getMissing().isEmpty());
613 			assertTrue(diff.getModified().isEmpty());
614 			assertEquals(1, diff.getConflicting().size());
615 			assertTrue(diff.getConflicting().contains("b"));
616 			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
617 					.get("b"));
618 			assertTrue(diff.getUntrackedFolders().isEmpty());
619 
620 			// reset file b to its master state without altering the index
621 			writeTrashFile("b", "second file content - master");
622 
623 			// we should have the same result
624 			iterator = new FileTreeIterator(db);
625 			diff = new IndexDiff(db, Constants.HEAD, iterator);
626 			diff.diff();
627 
628 			assertTrue(diff.getChanged().isEmpty());
629 			assertTrue(diff.getAdded().isEmpty());
630 			assertTrue(diff.getRemoved().isEmpty());
631 			assertTrue(diff.getMissing().isEmpty());
632 			assertTrue(diff.getModified().isEmpty());
633 			assertEquals(1, diff.getConflicting().size());
634 			assertTrue(diff.getConflicting().contains("b"));
635 			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
636 					.get("b"));
637 			assertTrue(diff.getUntrackedFolders().isEmpty());
638 		}
639 	}
640 
641 	@Test
642 	public void testStageState_simulated_bug() throws Exception {
643 		try (Git git = new Git(db)) {
644 			writeTrashFile("a", "content");
645 			git.add().addFilepattern("a").call();
646 			RevCommit initialCommit = git.commit().setMessage("initial commit")
647 					.call();
648 
649 			// create branch and add a new file
650 			final String branchName = Constants.R_HEADS + "branch";
651 			createBranch(initialCommit, branchName);
652 			checkoutBranch(branchName);
653 			writeTrashFile("b", "second file content - branch");
654 			git.add().addFilepattern("b").call();
655 			git.commit().setMessage("branch commit")
656 					.call();
657 
658 			// checkout master and add the same new file
659 			checkoutBranch(Constants.R_HEADS + Constants.MASTER);
660 			writeTrashFile("b", "second file content - master");
661 			git.add().addFilepattern("b").call();
662 			git.commit().setMessage("master commit").call();
663 
664 			// Simulate a failed merge of branch into master
665 			DirCacheBuilder builder = db.lockDirCache().builder();
666 			DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0,
667 					"content");
668 			builder.add(entry);
669 			entry = createEntry("b", FileMode.REGULAR_FILE, 2,
670 					"second file content - master");
671 			builder.add(entry);
672 			entry = createEntry("b", FileMode.REGULAR_FILE, 3,
673 					"second file content - branch");
674 			builder.add(entry);
675 			builder.commit();
676 
677 			FileTreeIterator iterator = new FileTreeIterator(db);
678 			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
679 			diff.diff();
680 
681 			assertTrue(diff.getChanged().isEmpty());
682 			assertTrue(diff.getAdded().isEmpty());
683 			assertTrue(diff.getRemoved().isEmpty());
684 			assertTrue(diff.getMissing().isEmpty());
685 			assertTrue(diff.getModified().isEmpty());
686 			assertEquals(1, diff.getConflicting().size());
687 			assertTrue(diff.getConflicting().contains("b"));
688 			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
689 					.get("b"));
690 			assertTrue(diff.getUntrackedFolders().isEmpty());
691 		}
692 	}
693 
694 	@Test
695 	public void testAutoCRLFInput() throws Exception {
696 		try (Git git = new Git(db)) {
697 			FileBasedConfig config = db.getConfig();
698 
699 			// Make sure core.autocrlf is false before adding
700 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
701 					ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
702 			config.save();
703 
704 			// File is already in repository with CRLF
705 			writeTrashFile("crlf.txt", "this\r\ncontains\r\ncrlf\r\n");
706 			git.add().addFilepattern("crlf.txt").call();
707 			git.commit().setMessage("Add crlf.txt").call();
708 
709 			// Now set core.autocrlf to input
710 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
711 					ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.INPUT);
712 			config.save();
713 
714 			FileTreeIterator iterator = new FileTreeIterator(db);
715 			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
716 			diff.diff();
717 
718 			assertTrue(
719 					"Expected no modified files, but there were: "
720 							+ diff.getModified(), diff.getModified().isEmpty());
721 		}
722 	}
723 
724 	private void verifyStageState(StageState expected, int... stages)
725 			throws IOException {
726 		DirCacheBuilder builder = db.lockDirCache().builder();
727 		for (int stage : stages) {
728 			DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE,
729 					stage, "content");
730 			builder.add(entry);
731 		}
732 		builder.commit();
733 
734 		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
735 				new FileTreeIterator(db));
736 		diff.diff();
737 
738 		assertEquals(
739 				"Conflict for entries in stages " + Arrays.toString(stages),
740 				expected, diff.getConflictingStageStates().get("a"));
741 	}
742 
743 	private void removeFromIndex(String path) throws IOException {
744 		final DirCache dirc = db.lockDirCache();
745 		final DirCacheEditor edit = dirc.editor();
746 		edit.add(new DirCacheEditor.DeletePath(path));
747 		if (!edit.commit())
748 			throw new IOException("could not commit");
749 	}
750 
751 	private void assumeUnchanged(String path) throws IOException {
752 		final DirCache dirc = db.lockDirCache();
753 		final DirCacheEntry ent = dirc.getEntry(path);
754 		if (ent != null)
755 			ent.setAssumeValid(true);
756 		dirc.write();
757 		if (!dirc.commit())
758 			throw new IOException("could not commit");
759 	}
760 
761 }