View Javadoc
1   /*
2    * Copyright (C) 2011, 2013 Dariusz Luksza <dariusz@luksza.org>
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  package org.eclipse.jgit.diff;
44  
45  import static org.eclipse.jgit.diff.DiffEntry.DEV_NULL;
46  import static org.eclipse.jgit.util.FileUtils.delete;
47  import static org.hamcrest.CoreMatchers.is;
48  import static org.hamcrest.CoreMatchers.notNullValue;
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.assertFalse;
51  import static org.junit.Assert.assertThat;
52  import static org.junit.Assert.assertTrue;
53  
54  import java.io.File;
55  import java.util.List;
56  
57  import org.eclipse.jgit.api.Git;
58  import org.eclipse.jgit.diff.DiffEntry.ChangeType;
59  import org.eclipse.jgit.dircache.DirCache;
60  import org.eclipse.jgit.dircache.DirCacheEditor;
61  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
62  import org.eclipse.jgit.internal.storage.file.FileRepository;
63  import org.eclipse.jgit.dircache.DirCacheEntry;
64  import org.eclipse.jgit.junit.JGitTestUtil;
65  import org.eclipse.jgit.junit.RepositoryTestCase;
66  import org.eclipse.jgit.lib.FileMode;
67  import org.eclipse.jgit.lib.Repository;
68  import org.eclipse.jgit.revwalk.RevCommit;
69  import org.eclipse.jgit.treewalk.EmptyTreeIterator;
70  import org.eclipse.jgit.treewalk.FileTreeIterator;
71  import org.eclipse.jgit.treewalk.TreeWalk;
72  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
73  import org.eclipse.jgit.treewalk.filter.TreeFilter;
74  import org.eclipse.jgit.util.FileUtils;
75  import org.junit.Test;
76  
77  public class DiffEntryTest extends RepositoryTestCase {
78  
79  	@Test
80  	public void shouldListAddedFileInInitialCommit() throws Exception {
81  		// given
82  		writeTrashFile("a.txt", "content");
83  		try (Git git = new Git(db);
84  				TreeWalk walk = new TreeWalk(db)) {
85  			git.add().addFilepattern("a.txt").call();
86  			RevCommit c = git.commit().setMessage("initial commit").call();
87  
88  			// when
89  			walk.addTree(new EmptyTreeIterator());
90  			walk.addTree(c.getTree());
91  			List<DiffEntry> result = DiffEntry.scan(walk);
92  
93  			// then
94  			assertThat(result, notNullValue());
95  			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
96  
97  			DiffEntry entry = result.get(0);
98  			assertThat(entry.getChangeType(), is(ChangeType.ADD));
99  			assertThat(entry.getNewPath(), is("a.txt"));
100 			assertThat(entry.getOldPath(), is(DEV_NULL));
101 		}
102 	}
103 
104 	@Test
105 	public void shouldListAddedFileBetweenTwoCommits() throws Exception {
106 		// given
107 		try (Git git = new Git(db);
108 				TreeWalk walk = new TreeWalk(db)) {
109 			RevCommit c1 = git.commit().setMessage("initial commit").call();
110 			writeTrashFile("a.txt", "content");
111 			git.add().addFilepattern("a.txt").call();
112 			RevCommit c2 = git.commit().setMessage("second commit").call();
113 
114 			// when
115 			walk.addTree(c1.getTree());
116 			walk.addTree(c2.getTree());
117 			List<DiffEntry> result = DiffEntry.scan(walk);
118 
119 			// then
120 			assertThat(result, notNullValue());
121 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
122 
123 			DiffEntry entry = result.get(0);
124 			assertThat(entry.getChangeType(), is(ChangeType.ADD));
125 			assertThat(entry.getNewPath(), is("a.txt"));
126 			assertThat(entry.getOldPath(), is(DEV_NULL));
127 		}
128 	}
129 
130 	@Test
131 	public void shouldListModificationBetweenTwoCommits() throws Exception {
132 		// given
133 		try (Git git = new Git(db);
134 				TreeWalk walk = new TreeWalk(db)) {
135 			File file = writeTrashFile("a.txt", "content");
136 			git.add().addFilepattern("a.txt").call();
137 			RevCommit c1 = git.commit().setMessage("initial commit").call();
138 			write(file, "new content");
139 			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
140 					.call();
141 
142 			// when
143 			walk.addTree(c1.getTree());
144 			walk.addTree(c2.getTree());
145 			List<DiffEntry> result = DiffEntry.scan(walk);
146 
147 			// then
148 			assertThat(result, notNullValue());
149 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
150 
151 			DiffEntry entry = result.get(0);
152 			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
153 			assertThat(entry.getNewPath(), is("a.txt"));
154 		}
155 	}
156 
157 	@Test
158 	public void shouldListDeletionBetweenTwoCommits() throws Exception {
159 		// given
160 		try (Git git = new Git(db);
161 				TreeWalk walk = new TreeWalk(db)) {
162 			File file = writeTrashFile("a.txt", "content");
163 			git.add().addFilepattern("a.txt").call();
164 			RevCommit c1 = git.commit().setMessage("initial commit").call();
165 			delete(file);
166 			RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt")
167 					.call();
168 
169 			// when
170 			walk.addTree(c1.getTree());
171 			walk.addTree(c2.getTree());
172 			List<DiffEntry> result = DiffEntry.scan(walk);
173 
174 			// then
175 			assertThat(result, notNullValue());
176 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
177 
178 			DiffEntry entry = result.get(0);
179 			assertThat(entry.getOldPath(), is("a.txt"));
180 			assertThat(entry.getNewPath(), is(DEV_NULL));
181 			assertThat(entry.getChangeType(), is(ChangeType.DELETE));
182 		}
183 	}
184 
185 	@Test
186 	public void shouldListModificationInDirWithoutModifiedTrees()
187 			throws Exception {
188 		// given
189 		try (Git git = new Git(db);
190 				TreeWalk walk = new TreeWalk(db)) {
191 			File tree = new File(new File(db.getWorkTree(), "a"), "b");
192 			FileUtils.mkdirs(tree);
193 			File file = new File(tree, "c.txt");
194 			FileUtils.createNewFile(file);
195 			write(file, "content");
196 			git.add().addFilepattern("a").call();
197 			RevCommit c1 = git.commit().setMessage("initial commit").call();
198 			write(file, "new line");
199 			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
200 					.call();
201 
202 			// when
203 			walk.addTree(c1.getTree());
204 			walk.addTree(c2.getTree());
205 			walk.setRecursive(true);
206 			List<DiffEntry> result = DiffEntry.scan(walk);
207 
208 			// then
209 			assertThat(result, notNullValue());
210 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
211 
212 			DiffEntry entry = result.get(0);
213 			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
214 			assertThat(entry.getNewPath(), is("a/b/c.txt"));
215 		}
216 	}
217 
218 	@Test
219 	public void shouldListModificationInDirWithModifiedTrees() throws Exception {
220 		// given
221 		try (Git git = new Git(db);
222 				TreeWalk walk = new TreeWalk(db)) {
223 			File tree = new File(new File(db.getWorkTree(), "a"), "b");
224 			FileUtils.mkdirs(tree);
225 			File file = new File(tree, "c.txt");
226 			FileUtils.createNewFile(file);
227 			write(file, "content");
228 			git.add().addFilepattern("a").call();
229 			RevCommit c1 = git.commit().setMessage("initial commit").call();
230 			write(file, "new line");
231 			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
232 					.call();
233 
234 			// when
235 			walk.addTree(c1.getTree());
236 			walk.addTree(c2.getTree());
237 			List<DiffEntry> result = DiffEntry.scan(walk, true);
238 
239 			// then
240 			assertThat(result, notNullValue());
241 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3)));
242 
243 			DiffEntry entry = result.get(0);
244 			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
245 			assertThat(entry.getNewPath(), is("a"));
246 
247 			entry = result.get(1);
248 			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
249 			assertThat(entry.getNewPath(), is("a/b"));
250 
251 			entry = result.get(2);
252 			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
253 			assertThat(entry.getNewPath(), is("a/b/c.txt"));
254 		}
255 	}
256 
257 	@Test
258 	public void shouldListChangesInWorkingTree() throws Exception {
259 		// given
260 		writeTrashFile("a.txt", "content");
261 		try (Git git = new Git(db);
262 				TreeWalk walk = new TreeWalk(db)) {
263 			git.add().addFilepattern("a.txt").call();
264 			RevCommit c = git.commit().setMessage("initial commit").call();
265 			writeTrashFile("b.txt", "new line");
266 
267 			// when
268 			walk.addTree(c.getTree());
269 			walk.addTree(new FileTreeIterator(db));
270 			List<DiffEntry> result = DiffEntry.scan(walk, true);
271 
272 			// then
273 			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
274 			DiffEntry entry = result.get(0);
275 
276 			assertThat(entry.getChangeType(), is(ChangeType.ADD));
277 			assertThat(entry.getNewPath(), is("b.txt"));
278 		}
279 	}
280 
281 	@Test
282 	public void shouldMarkEntriesWhenGivenMarkTreeFilter() throws Exception {
283 		// given
284 		try (Git git = new Git(db);
285 				TreeWalk walk = new TreeWalk(db)) {
286 			RevCommit c1 = git.commit().setMessage("initial commit").call();
287 			FileUtils.mkdir(new File(db.getWorkTree(), "b"));
288 			writeTrashFile("a.txt", "a");
289 			writeTrashFile("b/1.txt", "b1");
290 			writeTrashFile("b/2.txt", "b2");
291 			writeTrashFile("c.txt", "c");
292 			git.add().addFilepattern("a.txt").addFilepattern("b")
293 					.addFilepattern("c.txt").call();
294 			RevCommit c2 = git.commit().setMessage("second commit").call();
295 			TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt");
296 			TreeFilter filterB = PathFilterGroup.createFromStrings("b");
297 			TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt");
298 
299 			// when
300 			walk.addTree(c1.getTree());
301 			walk.addTree(c2.getTree());
302 			List<DiffEntry> result = DiffEntry.scan(walk, true, new TreeFilter[] {
303 					filterA, filterB, filterB2 });
304 
305 			// then
306 			assertThat(result, notNullValue());
307 			assertEquals(5, result.size());
308 
309 			DiffEntry entryA = result.get(0);
310 			DiffEntry entryB = result.get(1);
311 			DiffEntry entryB1 = result.get(2);
312 			DiffEntry entryB2 = result.get(3);
313 			DiffEntry entryC = result.get(4);
314 
315 			assertThat(entryA.getNewPath(), is("a.txt"));
316 			assertTrue(entryA.isMarked(0));
317 			assertFalse(entryA.isMarked(1));
318 			assertFalse(entryA.isMarked(2));
319 			assertEquals(1, entryA.getTreeFilterMarks());
320 
321 			assertThat(entryB.getNewPath(), is("b"));
322 			assertFalse(entryB.isMarked(0));
323 			assertTrue(entryB.isMarked(1));
324 			assertTrue(entryB.isMarked(2));
325 			assertEquals(6, entryB.getTreeFilterMarks());
326 
327 			assertThat(entryB1.getNewPath(), is("b/1.txt"));
328 			assertFalse(entryB1.isMarked(0));
329 			assertTrue(entryB1.isMarked(1));
330 			assertFalse(entryB1.isMarked(2));
331 			assertEquals(2, entryB1.getTreeFilterMarks());
332 
333 			assertThat(entryB2.getNewPath(), is("b/2.txt"));
334 			assertFalse(entryB2.isMarked(0));
335 			assertTrue(entryB2.isMarked(1));
336 			assertTrue(entryB2.isMarked(2));
337 			assertEquals(6, entryB2.getTreeFilterMarks());
338 
339 			assertThat(entryC.getNewPath(), is("c.txt"));
340 			assertFalse(entryC.isMarked(0));
341 			assertFalse(entryC.isMarked(1));
342 			assertFalse(entryC.isMarked(2));
343 			assertEquals(0, entryC.getTreeFilterMarks());
344 		}
345 	}
346 
347 	@Test(expected = IllegalArgumentException.class)
348 	public void shouldThrowIAEWhenTreeWalkHasLessThanTwoTrees()
349 			throws Exception {
350 		// given - we don't need anything here
351 
352 		// when
353 		try (TreeWalk walk = new TreeWalk(db)) {
354 			walk.addTree(new EmptyTreeIterator());
355 			DiffEntry.scan(walk);
356 		}
357 	}
358 
359 	@Test(expected = IllegalArgumentException.class)
360 	public void shouldThrowIAEWhenTreeWalkHasMoreThanTwoTrees()
361 			throws Exception {
362 		// given - we don't need anything here
363 
364 		// when
365 		try (TreeWalk walk = new TreeWalk(db)) {
366 			walk.addTree(new EmptyTreeIterator());
367 			walk.addTree(new EmptyTreeIterator());
368 			walk.addTree(new EmptyTreeIterator());
369 			DiffEntry.scan(walk);
370 		}
371 	}
372 
373 	@Test(expected = IllegalArgumentException.class)
374 	public void shouldThrowIAEWhenScanShouldIncludeTreesAndWalkIsRecursive()
375 			throws Exception {
376 		// given - we don't need anything here
377 
378 		// when
379 		try (TreeWalk walk = new TreeWalk(db)) {
380 			walk.addTree(new EmptyTreeIterator());
381 			walk.addTree(new EmptyTreeIterator());
382 			walk.setRecursive(true);
383 			DiffEntry.scan(walk, true);
384 		}
385 	}
386 
387 	@Test
388 	public void shouldReportFileModeChange() throws Exception {
389 		writeTrashFile("a.txt", "content");
390 		try (Git git = new Git(db);
391 				TreeWalk walk = new TreeWalk(db)) {
392 			git.add().addFilepattern("a.txt").call();
393 			RevCommit c1 = git.commit().setMessage("initial commit").call();
394 			DirCache cache = db.lockDirCache();
395 			DirCacheEditor editor = cache.editor();
396 			walk.addTree(c1.getTree());
397 			walk.setRecursive(true);
398 			assertTrue(walk.next());
399 
400 			editor.add(new PathEdit("a.txt") {
401 				@Override
402 				public void apply(DirCacheEntry ent) {
403 					ent.setFileMode(FileMode.EXECUTABLE_FILE);
404 					ent.setObjectId(walk.getObjectId(0));
405 				}
406 			});
407 			assertTrue(editor.commit());
408 			RevCommit c2 = git.commit().setMessage("second commit").call();
409 			walk.reset();
410 			walk.addTree(c1.getTree());
411 			walk.addTree(c2.getTree());
412 			List<DiffEntry> diffs = DiffEntry.scan(walk, false);
413 			assertEquals(1, diffs.size());
414 			DiffEntry diff = diffs.get(0);
415 			assertEquals(ChangeType.MODIFY,diff.getChangeType());
416 			assertEquals(diff.getOldId(), diff.getNewId());
417 			assertEquals("a.txt", diff.getOldPath());
418 			assertEquals(diff.getOldPath(), diff.getNewPath());
419 			assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode());
420 			assertEquals(FileMode.REGULAR_FILE, diff.getOldMode());
421 		}
422 	}
423 
424 	@Test
425 	public void shouldReportSubmoduleReplacedByFileMove() throws Exception {
426 		// Create a submodule
427 		FileRepository submoduleStandalone = createWorkRepository();
428 		JGitTestUtil.writeTrashFile(submoduleStandalone, "fileInSubmodule",
429 				"submodule");
430 		Git submoduleStandaloneGit = Git.wrap(submoduleStandalone);
431 		submoduleStandaloneGit.add().addFilepattern("fileInSubmodule").call();
432 		submoduleStandaloneGit.commit().setMessage("add file to submodule")
433 				.call();
434 
435 		Repository submodule_db = Git.wrap(db).submoduleAdd()
436 				.setPath("modules/submodule")
437 				.setURI(submoduleStandalone.getDirectory().toURI().toString())
438 				.call();
439 		File submodule_trash = submodule_db.getWorkTree();
440 		addRepoToClose(submodule_db);
441 		writeTrashFile("fileInRoot", "root");
442 		Git rootGit = Git.wrap(db);
443 		rootGit.add().addFilepattern("fileInRoot").call();
444 		rootGit.commit().setMessage("add submodule and root file").call();
445 		// Dummy change on fileInRoot
446 		writeTrashFile("fileInRoot", "changed");
447 		rootGit.add().addFilepattern("fileInRoot").call();
448 		RevCommit firstCommit = rootGit.commit().setMessage("change root file")
449 				.call();
450 		// Remove the submodule again and move fileInRoot into that subfolder
451 		rootGit.rm().setCached(true).addFilepattern("modules/submodule").call();
452 		recursiveDelete(submodule_trash);
453 		JGitTestUtil.deleteTrashFile(db, "fileInRoot");
454 		// Move the fileInRoot file
455 		writeTrashFile("modules/submodule/fileInRoot", "changed");
456 		rootGit.rm().addFilepattern("fileInRoot").addFilepattern("modules/")
457 				.call();
458 		rootGit.add().addFilepattern("modules/").call();
459 		RevCommit secondCommit = rootGit.commit()
460 				.setMessage("remove submodule and move root file")
461 				.call();
462 		// Diff should report submodule having been deleted and file moved
463 		// (deleted and added)
464 		try (TreeWalk walk = new TreeWalk(db)) {
465 			walk.addTree(firstCommit.getTree());
466 			walk.addTree(secondCommit.getTree());
467 			walk.setRecursive(true);
468 			List<DiffEntry> diffs = DiffEntry.scan(walk);
469 			assertEquals(3, diffs.size());
470 			DiffEntry e = diffs.get(0);
471 			assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
472 			assertEquals("fileInRoot", e.getOldPath());
473 			e = diffs.get(1);
474 			assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
475 			assertEquals("modules/submodule", e.getOldPath());
476 			assertEquals(FileMode.GITLINK, e.getOldMode());
477 			e = diffs.get(2);
478 			assertEquals(DiffEntry.ChangeType.ADD, e.getChangeType());
479 			assertEquals("modules/submodule/fileInRoot", e.getNewPath());
480 		}
481 
482 	}
483 }