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