View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc.
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.api;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertNotNull;
47  
48  import java.io.File;
49  
50  import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
51  import org.eclipse.jgit.api.ResetCommand.ResetType;
52  import org.eclipse.jgit.blame.BlameResult;
53  import org.eclipse.jgit.diff.RawTextComparator;
54  import org.eclipse.jgit.junit.RepositoryTestCase;
55  import org.eclipse.jgit.lib.ConfigConstants;
56  import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
57  import org.eclipse.jgit.revwalk.RevCommit;
58  import org.eclipse.jgit.storage.file.FileBasedConfig;
59  import org.junit.Test;
60  
61  /**
62   * Unit tests of {@link BlameCommand}
63   */
64  public class BlameCommandTest extends RepositoryTestCase {
65  
66  	private static String join(String... lines) {
67  		StringBuilder joined = new StringBuilder();
68  		for (String line : lines)
69  			joined.append(line).append('\n');
70  		return joined.toString();
71  	}
72  
73  	@Test
74  	public void testSingleRevision() throws Exception {
75  		try (Git git = new Git(db)) {
76  			String[] content = new String[] { "first", "second", "third" };
77  
78  			writeTrashFile("file.txt", join(content));
79  			git.add().addFilepattern("file.txt").call();
80  			RevCommit commit = git.commit().setMessage("create file").call();
81  
82  			BlameCommand command = new BlameCommand(db);
83  			command.setFilePath("file.txt");
84  			BlameResult lines = command.call();
85  			assertNotNull(lines);
86  			assertEquals(3, lines.getResultContents().size());
87  
88  			for (int i = 0; i < 3; i++) {
89  				assertEquals(commit, lines.getSourceCommit(i));
90  				assertEquals(i, lines.getSourceLine(i));
91  			}
92  		}
93  	}
94  
95  	@Test
96  	public void testTwoRevisions() throws Exception {
97  		try (Git git = new Git(db)) {
98  			String[] content1 = new String[] { "first", "second" };
99  			writeTrashFile("file.txt", join(content1));
100 			git.add().addFilepattern("file.txt").call();
101 			RevCommit commit1 = git.commit().setMessage("create file").call();
102 
103 			String[] content2 = new String[] { "first", "second", "third" };
104 			writeTrashFile("file.txt", join(content2));
105 			git.add().addFilepattern("file.txt").call();
106 			RevCommit commit2 = git.commit().setMessage("create file").call();
107 
108 			BlameCommand command = new BlameCommand(db);
109 			command.setFilePath("file.txt");
110 			BlameResult lines = command.call();
111 			assertEquals(3, lines.getResultContents().size());
112 
113 			assertEquals(commit1, lines.getSourceCommit(0));
114 			assertEquals(0, lines.getSourceLine(0));
115 
116 			assertEquals(commit1, lines.getSourceCommit(1));
117 			assertEquals(1, lines.getSourceLine(1));
118 
119 			assertEquals(commit2, lines.getSourceCommit(2));
120 			assertEquals(2, lines.getSourceLine(2));
121 		}
122 	}
123 
124 	@Test
125 	public void testRename() throws Exception {
126 		testRename("file1.txt", "file2.txt");
127 	}
128 
129 	@Test
130 	public void testRenameInSubDir() throws Exception {
131 		testRename("subdir/file1.txt", "subdir/file2.txt");
132 	}
133 
134 	@Test
135 	public void testMoveToOtherDir() throws Exception {
136 		testRename("subdir/file1.txt", "otherdir/file1.txt");
137 	}
138 
139 	private void testRename(String sourcePath, String destPath)
140 			throws Exception {
141 		try (Git git = new Git(db)) {
142 			String[] content1 = new String[] { "a", "b", "c" };
143 			writeTrashFile(sourcePath, join(content1));
144 			git.add().addFilepattern(sourcePath).call();
145 			RevCommit commit1 = git.commit().setMessage("create file").call();
146 
147 			writeTrashFile(destPath, join(content1));
148 			git.add().addFilepattern(destPath).call();
149 			git.rm().addFilepattern(sourcePath).call();
150 			git.commit().setMessage("moving file").call();
151 
152 			String[] content2 = new String[] { "a", "b", "c2" };
153 			writeTrashFile(destPath, join(content2));
154 			git.add().addFilepattern(destPath).call();
155 			RevCommit commit3 = git.commit().setMessage("editing file").call();
156 
157 			BlameCommand command = new BlameCommand(db);
158 			command.setFollowFileRenames(true);
159 			command.setFilePath(destPath);
160 			BlameResult lines = command.call();
161 
162 			assertEquals(commit1, lines.getSourceCommit(0));
163 			assertEquals(0, lines.getSourceLine(0));
164 			assertEquals(sourcePath, lines.getSourcePath(0));
165 
166 			assertEquals(commit1, lines.getSourceCommit(1));
167 			assertEquals(1, lines.getSourceLine(1));
168 			assertEquals(sourcePath, lines.getSourcePath(1));
169 
170 			assertEquals(commit3, lines.getSourceCommit(2));
171 			assertEquals(2, lines.getSourceLine(2));
172 			assertEquals(destPath, lines.getSourcePath(2));
173 		}
174 	}
175 
176 	@Test
177 	public void testTwoRenames() throws Exception {
178 		try (Git git = new Git(db)) {
179 			// Commit 1: Add file.txt
180 			String[] content1 = new String[] { "a" };
181 			writeTrashFile("file.txt", join(content1));
182 			git.add().addFilepattern("file.txt").call();
183 			RevCommit commit1 = git.commit().setMessage("create file").call();
184 
185 			// Commit 2: Rename to file1.txt
186 			writeTrashFile("file1.txt", join(content1));
187 			git.add().addFilepattern("file1.txt").call();
188 			git.rm().addFilepattern("file.txt").call();
189 			git.commit().setMessage("moving file").call();
190 
191 			// Commit 3: Edit file1.txt
192 			String[] content2 = new String[] { "a", "b" };
193 			writeTrashFile("file1.txt", join(content2));
194 			git.add().addFilepattern("file1.txt").call();
195 			RevCommit commit3 = git.commit().setMessage("editing file").call();
196 
197 			// Commit 4: Rename to file2.txt
198 			writeTrashFile("file2.txt", join(content2));
199 			git.add().addFilepattern("file2.txt").call();
200 			git.rm().addFilepattern("file1.txt").call();
201 			git.commit().setMessage("moving file again").call();
202 
203 			BlameCommand command = new BlameCommand(db);
204 			command.setFollowFileRenames(true);
205 			command.setFilePath("file2.txt");
206 			BlameResult lines = command.call();
207 
208 			assertEquals(commit1, lines.getSourceCommit(0));
209 			assertEquals(0, lines.getSourceLine(0));
210 			assertEquals("file.txt", lines.getSourcePath(0));
211 
212 			assertEquals(commit3, lines.getSourceCommit(1));
213 			assertEquals(1, lines.getSourceLine(1));
214 			assertEquals("file1.txt", lines.getSourcePath(1));
215 		}
216 	}
217 
218 	@Test
219 	public void testDeleteTrailingLines() throws Exception {
220 		try (Git git = new Git(db)) {
221 			String[] content1 = new String[] { "a", "b", "c", "d" };
222 			String[] content2 = new String[] { "a", "b" };
223 
224 			writeTrashFile("file.txt", join(content2));
225 			git.add().addFilepattern("file.txt").call();
226 			RevCommit commit1 = git.commit().setMessage("create file").call();
227 
228 			writeTrashFile("file.txt", join(content1));
229 			git.add().addFilepattern("file.txt").call();
230 			git.commit().setMessage("edit file").call();
231 
232 			writeTrashFile("file.txt", join(content2));
233 			git.add().addFilepattern("file.txt").call();
234 			git.commit().setMessage("edit file").call();
235 
236 			BlameCommand command = new BlameCommand(db);
237 
238 			command.setFilePath("file.txt");
239 			BlameResult lines = command.call();
240 			assertEquals(content2.length, lines.getResultContents().size());
241 
242 			assertEquals(commit1, lines.getSourceCommit(0));
243 			assertEquals(commit1, lines.getSourceCommit(1));
244 
245 			assertEquals(0, lines.getSourceLine(0));
246 			assertEquals(1, lines.getSourceLine(1));
247 		}
248 	}
249 
250 	@Test
251 	public void testDeleteMiddleLines() throws Exception {
252 		try (Git git = new Git(db)) {
253 			String[] content1 = new String[] { "a", "b", "c", "d", "e" };
254 			String[] content2 = new String[] { "a", "c", "e" };
255 
256 			writeTrashFile("file.txt", join(content2));
257 			git.add().addFilepattern("file.txt").call();
258 			RevCommit commit1 = git.commit().setMessage("edit file").call();
259 
260 			writeTrashFile("file.txt", join(content1));
261 			git.add().addFilepattern("file.txt").call();
262 			git.commit().setMessage("edit file").call();
263 
264 			writeTrashFile("file.txt", join(content2));
265 			git.add().addFilepattern("file.txt").call();
266 			git.commit().setMessage("edit file").call();
267 
268 			BlameCommand command = new BlameCommand(db);
269 
270 			command.setFilePath("file.txt");
271 			BlameResult lines = command.call();
272 			assertEquals(content2.length, lines.getResultContents().size());
273 
274 			assertEquals(commit1, lines.getSourceCommit(0));
275 			assertEquals(0, lines.getSourceLine(0));
276 
277 			assertEquals(commit1, lines.getSourceCommit(1));
278 			assertEquals(1, lines.getSourceLine(1));
279 
280 			assertEquals(commit1, lines.getSourceCommit(2));
281 			assertEquals(2, lines.getSourceLine(2));
282 		}
283 	}
284 
285 	@Test
286 	public void testEditAllLines() throws Exception {
287 		try (Git git = new Git(db)) {
288 			String[] content1 = new String[] { "a", "1" };
289 			String[] content2 = new String[] { "b", "2" };
290 
291 			writeTrashFile("file.txt", join(content1));
292 			git.add().addFilepattern("file.txt").call();
293 			git.commit().setMessage("edit file").call();
294 
295 			writeTrashFile("file.txt", join(content2));
296 			git.add().addFilepattern("file.txt").call();
297 			RevCommit commit2 = git.commit().setMessage("create file").call();
298 
299 			BlameCommand command = new BlameCommand(db);
300 
301 			command.setFilePath("file.txt");
302 			BlameResult lines = command.call();
303 			assertEquals(content2.length, lines.getResultContents().size());
304 			assertEquals(commit2, lines.getSourceCommit(0));
305 			assertEquals(commit2, lines.getSourceCommit(1));
306 		}
307 	}
308 
309 	@Test
310 	public void testMiddleClearAllLines() throws Exception {
311 		try (Git git = new Git(db)) {
312 			String[] content1 = new String[] { "a", "b", "c" };
313 
314 			writeTrashFile("file.txt", join(content1));
315 			git.add().addFilepattern("file.txt").call();
316 			git.commit().setMessage("edit file").call();
317 
318 			writeTrashFile("file.txt", "");
319 			git.add().addFilepattern("file.txt").call();
320 			git.commit().setMessage("create file").call();
321 
322 			writeTrashFile("file.txt", join(content1));
323 			git.add().addFilepattern("file.txt").call();
324 			RevCommit commit3 = git.commit().setMessage("edit file").call();
325 
326 			BlameCommand command = new BlameCommand(db);
327 
328 			command.setFilePath("file.txt");
329 			BlameResult lines = command.call();
330 			assertEquals(content1.length, lines.getResultContents().size());
331 			assertEquals(commit3, lines.getSourceCommit(0));
332 			assertEquals(commit3, lines.getSourceCommit(1));
333 			assertEquals(commit3, lines.getSourceCommit(2));
334 		}
335 	}
336 
337 	@Test
338 	public void testCoreAutoCrlf1() throws Exception {
339 		testCoreAutoCrlf(AutoCRLF.INPUT, AutoCRLF.FALSE);
340 	}
341 
342 	@Test
343 	public void testCoreAutoCrlf2() throws Exception {
344 		testCoreAutoCrlf(AutoCRLF.FALSE, AutoCRLF.FALSE);
345 	}
346 
347 	@Test
348 	public void testCoreAutoCrlf3() throws Exception {
349 		testCoreAutoCrlf(AutoCRLF.INPUT, AutoCRLF.INPUT);
350 	}
351 
352 	@Test
353 	public void testCoreAutoCrlf4() throws Exception {
354 		testCoreAutoCrlf(AutoCRLF.FALSE, AutoCRLF.INPUT);
355 	}
356 
357 	@Test
358 	public void testCoreAutoCrlf5() throws Exception {
359 		testCoreAutoCrlf(AutoCRLF.INPUT, AutoCRLF.TRUE);
360 	}
361 
362 	private void testCoreAutoCrlf(AutoCRLF modeForCommitting,
363 			AutoCRLF modeForReset) throws Exception {
364 		try (Git git = new Git(db)) {
365 			FileBasedConfig config = db.getConfig();
366 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
367 					ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForCommitting);
368 			config.save();
369 
370 			String joinedCrlf = "a\r\nb\r\nc\r\n";
371 			File trashFile = writeTrashFile("file.txt", joinedCrlf);
372 			git.add().addFilepattern("file.txt").call();
373 			RevCommit commit = git.commit().setMessage("create file").call();
374 
375 			// re-create file from the repo
376 			trashFile.delete();
377 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
378 					ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForReset);
379 			config.save();
380 			git.reset().setMode(ResetType.HARD).call();
381 
382 			BlameCommand command = new BlameCommand(db);
383 			command.setFilePath("file.txt");
384 			BlameResult lines = command.call();
385 
386 			assertEquals(3, lines.getResultContents().size());
387 			assertEquals(commit, lines.getSourceCommit(0));
388 			assertEquals(commit, lines.getSourceCommit(1));
389 			assertEquals(commit, lines.getSourceCommit(2));
390 		}
391 	}
392 
393 	@Test
394 	public void testConflictingMerge1() throws Exception {
395 		try (Git git = new Git(db)) {
396 			RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
397 					"master");
398 
399 			git.checkout().setName("side").setCreateBranch(true)
400 					.setStartPoint(base).call();
401 			RevCommit side = commitFile("file.txt",
402 					join("0", "1 side", "2", "3 on side", "4"), "side");
403 
404 			commitFile("file.txt", join("0", "1", "2"), "master");
405 
406 			checkoutBranch("refs/heads/master");
407 			git.merge().include(side).call();
408 
409 			// The merge results in a conflict, which we resolve using mostly the
410 			// side branch contents. Especially the "4" survives.
411 			RevCommit merge = commitFile("file.txt",
412 					join("0", "1 side", "2", "3 resolved", "4"), "master");
413 
414 			BlameCommand command = new BlameCommand(db);
415 			command.setFilePath("file.txt");
416 			BlameResult lines = command.call();
417 
418 			assertEquals(5, lines.getResultContents().size());
419 			assertEquals(base, lines.getSourceCommit(0));
420 			assertEquals(side, lines.getSourceCommit(1));
421 			assertEquals(base, lines.getSourceCommit(2));
422 			assertEquals(merge, lines.getSourceCommit(3));
423 			assertEquals(base, lines.getSourceCommit(4));
424 		}
425 	}
426 
427 	// this test inverts the order of the master and side commit and is
428 	// otherwise identical to testConflictingMerge1
429 	@Test
430 	public void testConflictingMerge2() throws Exception {
431 		try (Git git = new Git(db)) {
432 			RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
433 					"master");
434 
435 			commitFile("file.txt", join("0", "1", "2"), "master");
436 
437 			git.checkout().setName("side").setCreateBranch(true)
438 					.setStartPoint(base).call();
439 			RevCommit side = commitFile("file.txt",
440 					join("0", "1 side", "2", "3 on side", "4"), "side");
441 
442 			checkoutBranch("refs/heads/master");
443 			git.merge().include(side).call();
444 
445 			// The merge results in a conflict, which we resolve using mostly the
446 			// side branch contents. Especially the "4" survives.
447 			RevCommit merge = commitFile("file.txt",
448 					join("0", "1 side", "2", "3 resolved", "4"), "master");
449 
450 			BlameCommand command = new BlameCommand(db);
451 			command.setFilePath("file.txt");
452 			BlameResult lines = command.call();
453 
454 			assertEquals(5, lines.getResultContents().size());
455 			assertEquals(base, lines.getSourceCommit(0));
456 			assertEquals(side, lines.getSourceCommit(1));
457 			assertEquals(base, lines.getSourceCommit(2));
458 			assertEquals(merge, lines.getSourceCommit(3));
459 			assertEquals(base, lines.getSourceCommit(4));
460 		}
461 	}
462 
463 	@Test
464 	public void testWhitespaceMerge() throws Exception {
465 		try (Git git = new Git(db)) {
466 			RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master");
467 			RevCommit side = commitFile("file.txt", join("0", "1", "   2 side  "),
468 					"side");
469 
470 			checkoutBranch("refs/heads/master");
471 			git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call();
472 
473 			// change whitespace, so the merge content is not identical to side, but
474 			// is the same when ignoring whitespace
475 			writeTrashFile("file.txt", join("0", "1", "2 side"));
476 			RevCommit merge = git.commit().setAll(true).setMessage("merge")
477 					.setAmend(true)
478 					.call();
479 
480 			BlameCommand command = new BlameCommand(db);
481 			command.setFilePath("file.txt")
482 					.setTextComparator(RawTextComparator.WS_IGNORE_ALL)
483 					.setStartCommit(merge.getId());
484 			BlameResult lines = command.call();
485 
486 			assertEquals(3, lines.getResultContents().size());
487 			assertEquals(base, lines.getSourceCommit(0));
488 			assertEquals(base, lines.getSourceCommit(1));
489 			assertEquals(side, lines.getSourceCommit(2));
490 		}
491 	}
492 }