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>
6    * and other copyright owners as documented in the project's IP log.
7    *
8    * This program and the accompanying materials are made available
9    * under the terms of the Eclipse Distribution License v1.0 which
10   * accompanies this distribution, is reproduced below, and is
11   * available at http://www.eclipse.org/org/documents/edl-v10.php
12   *
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or
16   * without modification, are permitted provided that the following
17   * conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright
20   *   notice, this list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above
23   *   copyright notice, this list of conditions and the following
24   *   disclaimer in the documentation and/or other materials provided
25   *   with the distribution.
26   *
27   * - Neither the name of the Eclipse Foundation, Inc. nor the
28   *   names of its contributors may be used to endorse or promote
29   *   products derived from this software without specific prior
30   *   written permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45   */
46  
47  package org.eclipse.jgit.lib;
48  
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.assertFalse;
51  import static org.junit.Assert.assertTrue;
52  
53  import java.io.File;
54  import java.io.FileNotFoundException;
55  import java.io.IOException;
56  import java.util.Arrays;
57  import java.util.Collections;
58  import java.util.HashSet;
59  import java.util.TreeSet;
60  
61  import org.eclipse.jgit.api.Git;
62  import org.eclipse.jgit.api.MergeResult;
63  import org.eclipse.jgit.api.MergeResult.MergeStatus;
64  import org.eclipse.jgit.api.errors.GitAPIException;
65  import org.eclipse.jgit.dircache.DirCache;
66  import org.eclipse.jgit.dircache.DirCacheBuilder;
67  import org.eclipse.jgit.dircache.DirCacheEditor;
68  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
69  import org.eclipse.jgit.dircache.DirCacheEntry;
70  import org.eclipse.jgit.junit.RepositoryTestCase;
71  import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
72  import org.eclipse.jgit.lib.IndexDiff.StageState;
73  import org.eclipse.jgit.merge.MergeStrategy;
74  import org.eclipse.jgit.revwalk.RevCommit;
75  import org.eclipse.jgit.storage.file.FileBasedConfig;
76  import org.eclipse.jgit.treewalk.FileTreeIterator;
77  import org.eclipse.jgit.util.IO;
78  import org.junit.Test;
79  
80  @SuppressWarnings("deprecation")
81  public class IndexDiffTest extends RepositoryTestCase {
82  
83  	static PathEdit add(final Repository db, final File workdir,
84  			final String path) throws FileNotFoundException, IOException {
85  		ObjectInserter inserter = db.newObjectInserter();
86  		final File f = new File(workdir, path);
87  		final ObjectId id = inserter.insert(Constants.OBJ_BLOB,
88  				IO.readFully(f));
89  		return new PathEdit(path) {
90  			public void apply(DirCacheEntry ent) {
91  				ent.setFileMode(FileMode.REGULAR_FILE);
92  				ent.setLength(f.length());
93  				ent.setObjectId(id);
94  			}
95  		};
96  	}
97  
98  	@Test
99  	public void testAdded() throws IOException {
100 		writeTrashFile("file1", "file1");
101 		writeTrashFile("dir/subfile", "dir/subfile");
102 		Tree tree = new Tree(db);
103 		tree.setId(insertTree(tree));
104 
105 		DirCache index = db.lockDirCache();
106 		DirCacheEditor editor = index.editor();
107 		editor.add(add(db, trash, "file1"));
108 		editor.add(add(db, trash, "dir/subfile"));
109 		editor.commit();
110 		FileTreeIterator iterator = new FileTreeIterator(db);
111 		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
112 		diff.diff();
113 		assertEquals(2, diff.getAdded().size());
114 		assertTrue(diff.getAdded().contains("file1"));
115 		assertTrue(diff.getAdded().contains("dir/subfile"));
116 		assertEquals(0, diff.getChanged().size());
117 		assertEquals(0, diff.getModified().size());
118 		assertEquals(0, diff.getRemoved().size());
119 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
120 	}
121 
122 	@Test
123 	public void testRemoved() throws IOException {
124 		writeTrashFile("file2", "file2");
125 		writeTrashFile("dir/file3", "dir/file3");
126 
127 		Tree tree = new Tree(db);
128 		tree.addFile("file2");
129 		tree.addFile("dir/file3");
130 		assertEquals(2, tree.memberCount());
131 		tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
132 		Tree tree2 = (Tree) tree.findTreeMember("dir");
133 		tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
134 		tree2.setId(insertTree(tree2));
135 		tree.setId(insertTree(tree));
136 
137 		FileTreeIterator iterator = new FileTreeIterator(db);
138 		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
139 		diff.diff();
140 		assertEquals(2, diff.getRemoved().size());
141 		assertTrue(diff.getRemoved().contains("file2"));
142 		assertTrue(diff.getRemoved().contains("dir/file3"));
143 		assertEquals(0, diff.getChanged().size());
144 		assertEquals(0, diff.getModified().size());
145 		assertEquals(0, diff.getAdded().size());
146 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
147 	}
148 
149 	@Test
150 	public void testModified() throws IOException, GitAPIException {
151 
152 		writeTrashFile("file2", "file2");
153 		writeTrashFile("dir/file3", "dir/file3");
154 
155 		Git git = new Git(db);
156 		git.add().addFilepattern("file2").addFilepattern("dir/file3").call();
157 
158 		writeTrashFile("dir/file3", "changed");
159 
160 		Tree tree = new Tree(db);
161 		tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
162 		tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
163 		assertEquals(2, tree.memberCount());
164 
165 		Tree tree2 = (Tree) tree.findTreeMember("dir");
166 		tree2.setId(insertTree(tree2));
167 		tree.setId(insertTree(tree));
168 		FileTreeIterator iterator = new FileTreeIterator(db);
169 		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
170 		diff.diff();
171 		assertEquals(2, diff.getChanged().size());
172 		assertTrue(diff.getChanged().contains("file2"));
173 		assertTrue(diff.getChanged().contains("dir/file3"));
174 		assertEquals(1, diff.getModified().size());
175 		assertTrue(diff.getModified().contains("dir/file3"));
176 		assertEquals(0, diff.getAdded().size());
177 		assertEquals(0, diff.getRemoved().size());
178 		assertEquals(0, diff.getMissing().size());
179 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
180 	}
181 
182 	@Test
183 	public void testConflicting() throws Exception {
184 		Git git = new Git(db);
185 
186 		writeTrashFile("a", "1\na\n3\n");
187 		writeTrashFile("b", "1\nb\n3\n");
188 		git.add().addFilepattern("a").addFilepattern("b").call();
189 		RevCommit initialCommit = git.commit().setMessage("initial").call();
190 
191 		// create side branch with two modifications
192 		createBranch(initialCommit, "refs/heads/side");
193 		checkoutBranch("refs/heads/side");
194 		writeTrashFile("a", "1\na(side)\n3\n");
195 		writeTrashFile("b", "1\nb\n3\n(side)");
196 		git.add().addFilepattern("a").addFilepattern("b").call();
197 		RevCommit secondCommit = git.commit().setMessage("side").call();
198 
199 		// update a on master to generate conflict
200 		checkoutBranch("refs/heads/master");
201 		writeTrashFile("a", "1\na(main)\n3\n");
202 		git.add().addFilepattern("a").call();
203 		git.commit().setMessage("main").call();
204 
205 		// merge side with master
206 		MergeResult result = git.merge().include(secondCommit.getId())
207 				.setStrategy(MergeStrategy.RESOLVE).call();
208 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
209 
210 		FileTreeIterator iterator = new FileTreeIterator(db);
211 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
212 		diff.diff();
213 
214 		assertEquals("[b]",
215 				new TreeSet<String>(diff.getChanged()).toString());
216 		assertEquals("[]", diff.getAdded().toString());
217 		assertEquals("[]", diff.getRemoved().toString());
218 		assertEquals("[]", diff.getMissing().toString());
219 		assertEquals("[]", diff.getModified().toString());
220 		assertEquals("[a]", diff.getConflicting().toString());
221 		assertEquals(StageState.BOTH_MODIFIED,
222 				diff.getConflictingStageStates().get("a"));
223 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
224 	}
225 
226 	@Test
227 	public void testConflictingDeletedAndModified() throws Exception {
228 		Git git = new Git(db);
229 
230 		writeTrashFile("a", "1\na\n3\n");
231 		writeTrashFile("b", "1\nb\n3\n");
232 		git.add().addFilepattern("a").addFilepattern("b").call();
233 		RevCommit initialCommit = git.commit().setMessage("initial").call();
234 
235 		// create side branch and delete "a"
236 		createBranch(initialCommit, "refs/heads/side");
237 		checkoutBranch("refs/heads/side");
238 		git.rm().addFilepattern("a").call();
239 		RevCommit secondCommit = git.commit().setMessage("side").call();
240 
241 		// update a on master to generate conflict
242 		checkoutBranch("refs/heads/master");
243 		writeTrashFile("a", "1\na(main)\n3\n");
244 		git.add().addFilepattern("a").call();
245 		git.commit().setMessage("main").call();
246 
247 		// merge side with master
248 		MergeResult result = git.merge().include(secondCommit.getId())
249 				.setStrategy(MergeStrategy.RESOLVE).call();
250 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
251 
252 		FileTreeIterator iterator = new FileTreeIterator(db);
253 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
254 		diff.diff();
255 
256 		assertEquals("[]", new TreeSet<String>(diff.getChanged()).toString());
257 		assertEquals("[]", diff.getAdded().toString());
258 		assertEquals("[]", diff.getRemoved().toString());
259 		assertEquals("[]", diff.getMissing().toString());
260 		assertEquals("[]", diff.getModified().toString());
261 		assertEquals("[a]", diff.getConflicting().toString());
262 		assertEquals(StageState.DELETED_BY_THEM,
263 				diff.getConflictingStageStates().get("a"));
264 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
265 	}
266 
267 	@Test
268 	public void testConflictingFromMultipleCreations() throws Exception {
269 		Git git = new Git(db);
270 
271 		writeTrashFile("a", "1\na\n3\n");
272 		git.add().addFilepattern("a").call();
273 		RevCommit initialCommit = git.commit().setMessage("initial").call();
274 
275 		createBranch(initialCommit, "refs/heads/side");
276 		checkoutBranch("refs/heads/side");
277 
278 		writeTrashFile("b", "1\nb(side)\n3\n");
279 		git.add().addFilepattern("b").call();
280 		RevCommit secondCommit = git.commit().setMessage("side").call();
281 
282 		checkoutBranch("refs/heads/master");
283 
284 		writeTrashFile("b", "1\nb(main)\n3\n");
285 		git.add().addFilepattern("b").call();
286 		git.commit().setMessage("main").call();
287 
288 		MergeResult result = git.merge().include(secondCommit.getId())
289 				.setStrategy(MergeStrategy.RESOLVE).call();
290 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
291 
292 		FileTreeIterator iterator = new FileTreeIterator(db);
293 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
294 		diff.diff();
295 
296 		assertEquals("[]", new TreeSet<String>(diff.getChanged()).toString());
297 		assertEquals("[]", diff.getAdded().toString());
298 		assertEquals("[]", diff.getRemoved().toString());
299 		assertEquals("[]", diff.getMissing().toString());
300 		assertEquals("[]", diff.getModified().toString());
301 		assertEquals("[b]", diff.getConflicting().toString());
302 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
303 	}
304 
305 	@Test
306 	public void testUnchangedSimple() throws IOException, GitAPIException {
307 		writeTrashFile("a.b", "a.b");
308 		writeTrashFile("a.c", "a.c");
309 		writeTrashFile("a=c", "a=c");
310 		writeTrashFile("a=d", "a=d");
311 		Git git = new Git(db);
312 		git.add().addFilepattern("a.b").call();
313 		git.add().addFilepattern("a.c").call();
314 		git.add().addFilepattern("a=c").call();
315 		git.add().addFilepattern("a=d").call();
316 
317 		Tree tree = new Tree(db);
318 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
319 		tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
320 		tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
321 		tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
322 		tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
323 
324 		tree.setId(insertTree(tree));
325 
326 		FileTreeIterator iterator = new FileTreeIterator(db);
327 		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
328 		diff.diff();
329 		assertEquals(0, diff.getChanged().size());
330 		assertEquals(0, diff.getAdded().size());
331 		assertEquals(0, diff.getRemoved().size());
332 		assertEquals(0, diff.getMissing().size());
333 		assertEquals(0, diff.getModified().size());
334 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
335 	}
336 
337 	/**
338 	 * This test has both files and directories that involve the tricky ordering
339 	 * used by Git.
340 	 *
341 	 * @throws IOException
342 	 * @throws GitAPIException
343 	 */
344 	@Test
345 	public void testUnchangedComplex() throws IOException, GitAPIException {
346 		Git git = new Git(db);
347 		writeTrashFile("a.b", "a.b");
348 		writeTrashFile("a.c", "a.c");
349 		writeTrashFile("a/b.b/b", "a/b.b/b");
350 		writeTrashFile("a/b", "a/b");
351 		writeTrashFile("a/c", "a/c");
352 		writeTrashFile("a=c", "a=c");
353 		writeTrashFile("a=d", "a=d");
354 		git.add().addFilepattern("a.b").addFilepattern("a.c")
355 				.addFilepattern("a/b.b/b").addFilepattern("a/b")
356 				.addFilepattern("a/c").addFilepattern("a=c")
357 				.addFilepattern("a=d").call();
358 
359 		Tree tree = new Tree(db);
360 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
361 		tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
362 		tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
363 		tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
364 		tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415"));
365 		tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
366 		tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
367 		tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
368 
369 		Tree tree3 = (Tree) tree.findTreeMember("a/b.b");
370 		tree3.setId(insertTree(tree3));
371 		Tree tree2 = (Tree) tree.findTreeMember("a");
372 		tree2.setId(insertTree(tree2));
373 		tree.setId(insertTree(tree));
374 
375 		FileTreeIterator iterator = new FileTreeIterator(db);
376 		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
377 		diff.diff();
378 		assertEquals(0, diff.getChanged().size());
379 		assertEquals(0, diff.getAdded().size());
380 		assertEquals(0, diff.getRemoved().size());
381 		assertEquals(0, diff.getMissing().size());
382 		assertEquals(0, diff.getModified().size());
383 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
384 	}
385 
386 	private ObjectId insertTree(Tree tree) throws IOException {
387 		try (ObjectInserter oi = db.newObjectInserter()) {
388 			ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format());
389 			oi.flush();
390 			return id;
391 		}
392 	}
393 
394 	/**
395 	 * A file is removed from the index but stays in the working directory. It
396 	 * is checked if IndexDiff detects this file as removed and untracked.
397 	 *
398 	 * @throws Exception
399 	 */
400 	@Test
401 	public void testRemovedUntracked() throws Exception{
402 		Git git = new Git(db);
403 		String path = "file";
404 		writeTrashFile(path, "content");
405 		git.add().addFilepattern(path).call();
406 		git.commit().setMessage("commit").call();
407 		removeFromIndex(path);
408 		FileTreeIterator iterator = new FileTreeIterator(db);
409 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
410 		diff.diff();
411 		assertTrue(diff.getRemoved().contains(path));
412 		assertTrue(diff.getUntracked().contains(path));
413 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
414 	}
415 
416 	/**
417 	 *
418 	 * @throws Exception
419 	 */
420 	@Test
421 	public void testUntrackedFolders() throws Exception {
422 		Git git = new Git(db);
423 
424 		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
425 				new FileTreeIterator(db));
426 		diff.diff();
427 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
428 
429 		writeTrashFile("readme", "");
430 		writeTrashFile("src/com/A.java", "");
431 		writeTrashFile("src/com/B.java", "");
432 		writeTrashFile("src/org/A.java", "");
433 		writeTrashFile("src/org/B.java", "");
434 		writeTrashFile("target/com/A.java", "");
435 		writeTrashFile("target/com/B.java", "");
436 		writeTrashFile("target/org/A.java", "");
437 		writeTrashFile("target/org/B.java", "");
438 
439 		git.add().addFilepattern("src").addFilepattern("readme").call();
440 		git.commit().setMessage("initial").call();
441 
442 		diff = new IndexDiff(db, Constants.HEAD,
443 				new FileTreeIterator(db));
444 		diff.diff();
445 		assertEquals(new HashSet<String>(Arrays.asList("target")),
446 				diff.getUntrackedFolders());
447 
448 		writeTrashFile("src/tst/A.java", "");
449 		writeTrashFile("src/tst/B.java", "");
450 
451 		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
452 		diff.diff();
453 		assertEquals(new HashSet<String>(Arrays.asList("target", "src/tst")),
454 				diff.getUntrackedFolders());
455 
456 		git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org")
457 				.call();
458 		git.commit().setMessage("second").call();
459 		writeTrashFile("src/org/C.java", "");
460 
461 		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
462 		diff.diff();
463 		assertEquals(
464 				new HashSet<String>(Arrays.asList("src/org", "src/tst",
465 						"target")),
466 				diff.getUntrackedFolders());
467 	}
468 
469 	/**
470 	 * Test that ignored folders aren't listed as untracked
471 	 *
472 	 * @throws Exception
473 	 */
474 	@Test
475 	public void testUntrackedNotIgnoredFolders() throws Exception {
476 		Git git = new Git(db);
477 
478 		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
479 				new FileTreeIterator(db));
480 		diff.diff();
481 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
482 
483 		writeTrashFile("readme", "");
484 		writeTrashFile("sr/com/X.java", "");
485 		writeTrashFile("src/com/A.java", "");
486 		writeTrashFile("src/org/B.java", "");
487 		writeTrashFile("srcs/org/Y.java", "");
488 		writeTrashFile("target/com/A.java", "");
489 		writeTrashFile("target/org/B.java", "");
490 		writeTrashFile(".gitignore", "/target\n/sr");
491 
492 		git.add().addFilepattern("readme").addFilepattern(".gitignore")
493 				.addFilepattern("srcs/").call();
494 		git.commit().setMessage("initial").call();
495 
496 		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
497 		diff.diff();
498 		assertEquals(new HashSet<String>(Arrays.asList("src")),
499 				diff.getUntrackedFolders());
500 
501 		git.add().addFilepattern("src").call();
502 		writeTrashFile("sr/com/X1.java", "");
503 		writeTrashFile("src/tst/A.java", "");
504 		writeTrashFile("src/tst/B.java", "");
505 		writeTrashFile("srcs/com/Y1.java", "");
506 		deleteTrashFile(".gitignore");
507 
508 		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
509 		diff.diff();
510 		assertEquals(
511 				new HashSet<String>(Arrays.asList("srcs/com", "sr", "src/tst",
512 						"target")),
513 				diff.getUntrackedFolders());
514 	}
515 
516 	@Test
517 	public void testAssumeUnchanged() throws Exception {
518 		Git git = new Git(db);
519 		String path = "file";
520 		writeTrashFile(path, "content");
521 		git.add().addFilepattern(path).call();
522 		String path2 = "file2";
523 		writeTrashFile(path2, "content");
524 		String path3 = "file3";
525 		writeTrashFile(path3, "some content");
526 		git.add().addFilepattern(path2).addFilepattern(path3).call();
527 		git.commit().setMessage("commit").call();
528 		assumeUnchanged(path2);
529 		assumeUnchanged(path3);
530 		writeTrashFile(path, "more content");
531 		deleteTrashFile(path3);
532 
533 		FileTreeIterator iterator = new FileTreeIterator(db);
534 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
535 		diff.diff();
536 		assertEquals(2, diff.getAssumeUnchanged().size());
537 		assertEquals(1, diff.getModified().size());
538 		assertEquals(0, diff.getChanged().size());
539 		assertTrue(diff.getAssumeUnchanged().contains("file2"));
540 		assertTrue(diff.getAssumeUnchanged().contains("file3"));
541 		assertTrue(diff.getModified().contains("file"));
542 
543 		git.add().addFilepattern(".").call();
544 
545 		iterator = new FileTreeIterator(db);
546 		diff = new IndexDiff(db, Constants.HEAD, iterator);
547 		diff.diff();
548 		assertEquals(2, diff.getAssumeUnchanged().size());
549 		assertEquals(0, diff.getModified().size());
550 		assertEquals(1, diff.getChanged().size());
551 		assertTrue(diff.getAssumeUnchanged().contains("file2"));
552 		assertTrue(diff.getAssumeUnchanged().contains("file3"));
553 		assertTrue(diff.getChanged().contains("file"));
554 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
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 		Git git = new Git(db);
581 
582 		writeTrashFile("a", "content");
583 		git.add().addFilepattern("a").call();
584 		RevCommit initialCommit = git.commit().setMessage("initial commit")
585 				.call();
586 
587 		// create branch and add a new file
588 		final String branchName = Constants.R_HEADS + "branch";
589 		createBranch(initialCommit, branchName);
590 		checkoutBranch(branchName);
591 		writeTrashFile("b", "second file content - branch");
592 		git.add().addFilepattern("b").call();
593 		RevCommit branchCommit = git.commit().setMessage("branch commit")
594 				.call();
595 
596 		// checkout master and add the same new file
597 		checkoutBranch(Constants.R_HEADS + Constants.MASTER);
598 		writeTrashFile("b", "second file content - master");
599 		git.add().addFilepattern("b").call();
600 		git.commit().setMessage("master commit").call();
601 
602 		// try and merge
603 		MergeResult result = git.merge().include(branchCommit).call();
604 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
605 
606 		FileTreeIterator iterator = new FileTreeIterator(db);
607 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
608 		diff.diff();
609 
610 		assertTrue(diff.getChanged().isEmpty());
611 		assertTrue(diff.getAdded().isEmpty());
612 		assertTrue(diff.getRemoved().isEmpty());
613 		assertTrue(diff.getMissing().isEmpty());
614 		assertTrue(diff.getModified().isEmpty());
615 		assertEquals(1, diff.getConflicting().size());
616 		assertTrue(diff.getConflicting().contains("b"));
617 		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
618 				.get("b"));
619 		assertTrue(diff.getUntrackedFolders().isEmpty());
620 
621 		// reset file b to its master state without altering the index
622 		writeTrashFile("b", "second file content - master");
623 
624 		// we should have the same result
625 		iterator = new FileTreeIterator(db);
626 		diff = new IndexDiff(db, Constants.HEAD, iterator);
627 		diff.diff();
628 
629 		assertTrue(diff.getChanged().isEmpty());
630 		assertTrue(diff.getAdded().isEmpty());
631 		assertTrue(diff.getRemoved().isEmpty());
632 		assertTrue(diff.getMissing().isEmpty());
633 		assertTrue(diff.getModified().isEmpty());
634 		assertEquals(1, diff.getConflicting().size());
635 		assertTrue(diff.getConflicting().contains("b"));
636 		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
637 				.get("b"));
638 		assertTrue(diff.getUntrackedFolders().isEmpty());
639 	}
640 
641 	@Test
642 	public void testStageState_simulated_bug() throws Exception {
643 		Git git = new Git(db);
644 
645 		writeTrashFile("a", "content");
646 		git.add().addFilepattern("a").call();
647 		RevCommit initialCommit = git.commit().setMessage("initial commit")
648 				.call();
649 
650 		// create branch and add a new file
651 		final String branchName = Constants.R_HEADS + "branch";
652 		createBranch(initialCommit, branchName);
653 		checkoutBranch(branchName);
654 		writeTrashFile("b", "second file content - branch");
655 		git.add().addFilepattern("b").call();
656 		git.commit().setMessage("branch commit")
657 				.call();
658 
659 		// checkout master and add the same new file
660 		checkoutBranch(Constants.R_HEADS + Constants.MASTER);
661 		writeTrashFile("b", "second file content - master");
662 		git.add().addFilepattern("b").call();
663 		git.commit().setMessage("master commit").call();
664 
665 		// Simulate a failed merge of branch into master
666 		DirCacheBuilder builder = db.lockDirCache().builder();
667 		DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0,
668 				"content");
669 		builder.add(entry);
670 		entry = createEntry("b", FileMode.REGULAR_FILE, 2,
671 				"second file content - master");
672 		builder.add(entry);
673 		entry = createEntry("b", FileMode.REGULAR_FILE, 3,
674 				"second file content - branch");
675 		builder.add(entry);
676 		builder.commit();
677 
678 		FileTreeIterator iterator = new FileTreeIterator(db);
679 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
680 		diff.diff();
681 
682 		assertTrue(diff.getChanged().isEmpty());
683 		assertTrue(diff.getAdded().isEmpty());
684 		assertTrue(diff.getRemoved().isEmpty());
685 		assertTrue(diff.getMissing().isEmpty());
686 		assertTrue(diff.getModified().isEmpty());
687 		assertEquals(1, diff.getConflicting().size());
688 		assertTrue(diff.getConflicting().contains("b"));
689 		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
690 				.get("b"));
691 		assertTrue(diff.getUntrackedFolders().isEmpty());
692 	}
693 
694 	@Test
695 	public void testAutoCRLFInput() throws Exception {
696 		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 	private void verifyStageState(StageState expected, int... stages)
724 			throws IOException {
725 		DirCacheBuilder builder = db.lockDirCache().builder();
726 		for (int stage : stages) {
727 			DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE,
728 					stage, "content");
729 			builder.add(entry);
730 		}
731 		builder.commit();
732 
733 		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
734 				new FileTreeIterator(db));
735 		diff.diff();
736 
737 		assertEquals(
738 				"Conflict for entries in stages " + Arrays.toString(stages),
739 				expected, diff.getConflictingStageStates().get("a"));
740 	}
741 
742 	private void removeFromIndex(String path) throws IOException {
743 		final DirCache dirc = db.lockDirCache();
744 		final DirCacheEditor edit = dirc.editor();
745 		edit.add(new DirCacheEditor.DeletePath(path));
746 		if (!edit.commit())
747 			throw new IOException("could not commit");
748 	}
749 
750 	private void assumeUnchanged(String path) throws IOException {
751 		final DirCache dirc = db.lockDirCache();
752 		final DirCacheEntry ent = dirc.getEntry(path);
753 		if (ent != null)
754 			ent.setAssumeValid(true);
755 		dirc.write();
756 		if (!dirc.commit())
757 			throw new IOException("could not commit");
758 	}
759 
760 }