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  		Git git = new Git(db);
76  
77  		String[] content = new String[] { "first", "second", "third" };
78  
79  		writeTrashFile("file.txt", join(content));
80  		git.add().addFilepattern("file.txt").call();
81  		RevCommit commit = git.commit().setMessage("create file").call();
82  
83  		BlameCommand command = new BlameCommand(db);
84  		command.setFilePath("file.txt");
85  		BlameResult lines = command.call();
86  		assertNotNull(lines);
87  		assertEquals(3, lines.getResultContents().size());
88  
89  		for (int i = 0; i < 3; i++) {
90  			assertEquals(commit, lines.getSourceCommit(i));
91  			assertEquals(i, lines.getSourceLine(i));
92  		}
93  	}
94  
95  	@Test
96  	public void testTwoRevisions() throws Exception {
97  		Git git = new Git(db);
98  
99  		String[] content1 = new String[] { "first", "second" };
100 		writeTrashFile("file.txt", join(content1));
101 		git.add().addFilepattern("file.txt").call();
102 		RevCommit commit1 = git.commit().setMessage("create file").call();
103 
104 		String[] content2 = new String[] { "first", "second", "third" };
105 		writeTrashFile("file.txt", join(content2));
106 		git.add().addFilepattern("file.txt").call();
107 		RevCommit commit2 = git.commit().setMessage("create file").call();
108 
109 		BlameCommand command = new BlameCommand(db);
110 		command.setFilePath("file.txt");
111 		BlameResult lines = command.call();
112 		assertEquals(3, lines.getResultContents().size());
113 
114 		assertEquals(commit1, lines.getSourceCommit(0));
115 		assertEquals(0, lines.getSourceLine(0));
116 
117 		assertEquals(commit1, lines.getSourceCommit(1));
118 		assertEquals(1, lines.getSourceLine(1));
119 
120 		assertEquals(commit2, lines.getSourceCommit(2));
121 		assertEquals(2, lines.getSourceLine(2));
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(final String sourcePath, final String destPath)
140 			throws Exception {
141 		Git git = new Git(db);
142 
143 		String[] content1 = new String[] { "a", "b", "c" };
144 		writeTrashFile(sourcePath, join(content1));
145 		git.add().addFilepattern(sourcePath).call();
146 		RevCommit commit1 = git.commit().setMessage("create file").call();
147 
148 		writeTrashFile(destPath, join(content1));
149 		git.add().addFilepattern(destPath).call();
150 		git.rm().addFilepattern(sourcePath).call();
151 		git.commit().setMessage("moving file").call();
152 
153 		String[] content2 = new String[] { "a", "b", "c2" };
154 		writeTrashFile(destPath, join(content2));
155 		git.add().addFilepattern(destPath).call();
156 		RevCommit commit3 = git.commit().setMessage("editing file").call();
157 
158 		BlameCommand command = new BlameCommand(db);
159 		command.setFollowFileRenames(true);
160 		command.setFilePath(destPath);
161 		BlameResult lines = command.call();
162 
163 		assertEquals(commit1, lines.getSourceCommit(0));
164 		assertEquals(0, lines.getSourceLine(0));
165 		assertEquals(sourcePath, lines.getSourcePath(0));
166 
167 		assertEquals(commit1, lines.getSourceCommit(1));
168 		assertEquals(1, lines.getSourceLine(1));
169 		assertEquals(sourcePath, lines.getSourcePath(1));
170 
171 		assertEquals(commit3, lines.getSourceCommit(2));
172 		assertEquals(2, lines.getSourceLine(2));
173 		assertEquals(destPath, lines.getSourcePath(2));
174 	}
175 
176 	@Test
177 	public void testTwoRenames() throws Exception {
178 		Git git = new Git(db);
179 
180 		// Commit 1: Add file.txt
181 		String[] content1 = new String[] { "a" };
182 		writeTrashFile("file.txt", join(content1));
183 		git.add().addFilepattern("file.txt").call();
184 		RevCommit commit1 = git.commit().setMessage("create file").call();
185 
186 		// Commit 2: Rename to file1.txt
187 		writeTrashFile("file1.txt", join(content1));
188 		git.add().addFilepattern("file1.txt").call();
189 		git.rm().addFilepattern("file.txt").call();
190 		git.commit().setMessage("moving file").call();
191 
192 		// Commit 3: Edit file1.txt
193 		String[] content2 = new String[] { "a", "b" };
194 		writeTrashFile("file1.txt", join(content2));
195 		git.add().addFilepattern("file1.txt").call();
196 		RevCommit commit3 = git.commit().setMessage("editing file").call();
197 
198 		// Commit 4: Rename to file2.txt
199 		writeTrashFile("file2.txt", join(content2));
200 		git.add().addFilepattern("file2.txt").call();
201 		git.rm().addFilepattern("file1.txt").call();
202 		git.commit().setMessage("moving file again").call();
203 
204 		BlameCommand command = new BlameCommand(db);
205 		command.setFollowFileRenames(true);
206 		command.setFilePath("file2.txt");
207 		BlameResult lines = command.call();
208 
209 		assertEquals(commit1, lines.getSourceCommit(0));
210 		assertEquals(0, lines.getSourceLine(0));
211 		assertEquals("file.txt", lines.getSourcePath(0));
212 
213 		assertEquals(commit3, lines.getSourceCommit(1));
214 		assertEquals(1, lines.getSourceLine(1));
215 		assertEquals("file1.txt", lines.getSourcePath(1));
216 	}
217 
218 	@Test
219 	public void testDeleteTrailingLines() throws Exception {
220 		Git git = new Git(db);
221 
222 		String[] content1 = new String[] { "a", "b", "c", "d" };
223 		String[] content2 = new String[] { "a", "b" };
224 
225 		writeTrashFile("file.txt", join(content2));
226 		git.add().addFilepattern("file.txt").call();
227 		RevCommit commit1 = git.commit().setMessage("create file").call();
228 
229 		writeTrashFile("file.txt", join(content1));
230 		git.add().addFilepattern("file.txt").call();
231 		git.commit().setMessage("edit file").call();
232 
233 		writeTrashFile("file.txt", join(content2));
234 		git.add().addFilepattern("file.txt").call();
235 		git.commit().setMessage("edit file").call();
236 
237 		BlameCommand command = new BlameCommand(db);
238 
239 		command.setFilePath("file.txt");
240 		BlameResult lines = command.call();
241 		assertEquals(content2.length, lines.getResultContents().size());
242 
243 		assertEquals(commit1, lines.getSourceCommit(0));
244 		assertEquals(commit1, lines.getSourceCommit(1));
245 
246 		assertEquals(0, lines.getSourceLine(0));
247 		assertEquals(1, lines.getSourceLine(1));
248 	}
249 
250 	@Test
251 	public void testDeleteMiddleLines() throws Exception {
252 		Git git = new Git(db);
253 
254 		String[] content1 = new String[] { "a", "b", "c", "d", "e" };
255 		String[] content2 = new String[] { "a", "c", "e" };
256 
257 		writeTrashFile("file.txt", join(content2));
258 		git.add().addFilepattern("file.txt").call();
259 		RevCommit commit1 = git.commit().setMessage("edit file").call();
260 
261 		writeTrashFile("file.txt", join(content1));
262 		git.add().addFilepattern("file.txt").call();
263 		git.commit().setMessage("edit file").call();
264 
265 		writeTrashFile("file.txt", join(content2));
266 		git.add().addFilepattern("file.txt").call();
267 		git.commit().setMessage("edit file").call();
268 
269 		BlameCommand command = new BlameCommand(db);
270 
271 		command.setFilePath("file.txt");
272 		BlameResult lines = command.call();
273 		assertEquals(content2.length, lines.getResultContents().size());
274 
275 		assertEquals(commit1, lines.getSourceCommit(0));
276 		assertEquals(0, lines.getSourceLine(0));
277 
278 		assertEquals(commit1, lines.getSourceCommit(1));
279 		assertEquals(1, lines.getSourceLine(1));
280 
281 		assertEquals(commit1, lines.getSourceCommit(2));
282 		assertEquals(2, lines.getSourceLine(2));
283 	}
284 
285 	@Test
286 	public void testEditAllLines() throws Exception {
287 		Git git = new Git(db);
288 
289 		String[] content1 = new String[] { "a", "1" };
290 		String[] content2 = new String[] { "b", "2" };
291 
292 		writeTrashFile("file.txt", join(content1));
293 		git.add().addFilepattern("file.txt").call();
294 		git.commit().setMessage("edit file").call();
295 
296 		writeTrashFile("file.txt", join(content2));
297 		git.add().addFilepattern("file.txt").call();
298 		RevCommit commit2 = git.commit().setMessage("create file").call();
299 
300 		BlameCommand command = new BlameCommand(db);
301 
302 		command.setFilePath("file.txt");
303 		BlameResult lines = command.call();
304 		assertEquals(content2.length, lines.getResultContents().size());
305 		assertEquals(commit2, lines.getSourceCommit(0));
306 		assertEquals(commit2, lines.getSourceCommit(1));
307 	}
308 
309 	@Test
310 	public void testMiddleClearAllLines() throws Exception {
311 		Git git = new Git(db);
312 
313 		String[] content1 = new String[] { "a", "b", "c" };
314 
315 		writeTrashFile("file.txt", join(content1));
316 		git.add().addFilepattern("file.txt").call();
317 		git.commit().setMessage("edit file").call();
318 
319 		writeTrashFile("file.txt", "");
320 		git.add().addFilepattern("file.txt").call();
321 		git.commit().setMessage("create file").call();
322 
323 		writeTrashFile("file.txt", join(content1));
324 		git.add().addFilepattern("file.txt").call();
325 		RevCommit commit3 = git.commit().setMessage("edit file").call();
326 
327 		BlameCommand command = new BlameCommand(db);
328 
329 		command.setFilePath("file.txt");
330 		BlameResult lines = command.call();
331 		assertEquals(content1.length, lines.getResultContents().size());
332 		assertEquals(commit3, lines.getSourceCommit(0));
333 		assertEquals(commit3, lines.getSourceCommit(1));
334 		assertEquals(commit3, lines.getSourceCommit(2));
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 		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 	@Test
393 	public void testConflictingMerge1() throws Exception {
394 		Git git = new Git(db);
395 
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 	// this test inverts the order of the master and side commit and is
427 	// otherwise identical to testConflictingMerge1
428 	@Test
429 	public void testConflictingMerge2() throws Exception {
430 		Git git = new Git(db);
431 
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 	@Test
463 	public void testWhitespaceMerge() throws Exception {
464 		Git git = new Git(db);
465 		RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master");
466 		RevCommit side = commitFile("file.txt", join("0", "1", "   2 side  "),
467 				"side");
468 
469 		checkoutBranch("refs/heads/master");
470 		git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call();
471 
472 		// change whitespace, so the merge content is not identical to side, but
473 		// is the same when ignoring whitespace
474 		writeTrashFile("file.txt", join("0", "1", "2 side"));
475 		RevCommit merge = git.commit().setAll(true).setMessage("merge")
476 				.setAmend(true)
477 				.call();
478 
479 		BlameCommand command = new BlameCommand(db);
480 		command.setFilePath("file.txt")
481 				.setTextComparator(RawTextComparator.WS_IGNORE_ALL)
482 				.setStartCommit(merge.getId());
483 		BlameResult lines = command.call();
484 
485 		assertEquals(3, lines.getResultContents().size());
486 		assertEquals(base, lines.getSourceCommit(0));
487 		assertEquals(base, lines.getSourceCommit(1));
488 		assertEquals(side, lines.getSourceCommit(2));
489 	}
490 }