View Javadoc
1   /*
2    * Copyright (C) 2011-2012, GitHub Inc. 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.api;
11  
12  import static org.junit.Assert.assertEquals;
13  import static org.junit.Assert.assertNotEquals;
14  import static org.junit.Assert.assertNotNull;
15  import static org.junit.Assert.assertNull;
16  import static org.junit.Assert.assertSame;
17  import static org.junit.Assert.assertTrue;
18  import static org.junit.Assert.fail;
19  
20  import java.io.File;
21  import java.util.Date;
22  import java.util.List;
23  import java.util.TimeZone;
24  import java.util.concurrent.atomic.AtomicInteger;
25  
26  import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
27  import org.eclipse.jgit.api.errors.CanceledException;
28  import org.eclipse.jgit.api.errors.EmptyCommitException;
29  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
30  import org.eclipse.jgit.diff.DiffEntry;
31  import org.eclipse.jgit.dircache.DirCache;
32  import org.eclipse.jgit.dircache.DirCacheBuilder;
33  import org.eclipse.jgit.dircache.DirCacheEntry;
34  import org.eclipse.jgit.junit.RepositoryTestCase;
35  import org.eclipse.jgit.junit.time.TimeUtil;
36  import org.eclipse.jgit.lib.CommitBuilder;
37  import org.eclipse.jgit.lib.ConfigConstants;
38  import org.eclipse.jgit.lib.Constants;
39  import org.eclipse.jgit.lib.FileMode;
40  import org.eclipse.jgit.lib.GpgSigner;
41  import org.eclipse.jgit.lib.ObjectId;
42  import org.eclipse.jgit.lib.PersonIdent;
43  import org.eclipse.jgit.lib.RefUpdate;
44  import org.eclipse.jgit.lib.RefUpdate.Result;
45  import org.eclipse.jgit.lib.ReflogEntry;
46  import org.eclipse.jgit.lib.Repository;
47  import org.eclipse.jgit.lib.StoredConfig;
48  import org.eclipse.jgit.revwalk.RevCommit;
49  import org.eclipse.jgit.storage.file.FileBasedConfig;
50  import org.eclipse.jgit.submodule.SubmoduleWalk;
51  import org.eclipse.jgit.transport.CredentialsProvider;
52  import org.eclipse.jgit.treewalk.TreeWalk;
53  import org.eclipse.jgit.treewalk.filter.TreeFilter;
54  import org.eclipse.jgit.util.FS;
55  import org.junit.Ignore;
56  import org.junit.Test;
57  
58  /**
59   * Unit tests of {@link CommitCommand}.
60   */
61  public class CommitCommandTest extends RepositoryTestCase {
62  
63  	@Test
64  	public void testExecutableRetention() throws Exception {
65  		StoredConfig config = db.getConfig();
66  		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
67  				ConfigConstants.CONFIG_KEY_FILEMODE, true);
68  		config.save();
69  
70  		FS executableFs = new FS() {
71  
72  			@Override
73  			public boolean supportsExecute() {
74  				return true;
75  			}
76  
77  			@Override
78  			public boolean setExecute(File f, boolean canExec) {
79  				return true;
80  			}
81  
82  			@Override
83  			public ProcessBuilder runInShell(String cmd, String[] args) {
84  				return null;
85  			}
86  
87  			@Override
88  			public boolean retryFailedLockFileCommit() {
89  				return false;
90  			}
91  
92  			@Override
93  			public FS newInstance() {
94  				return this;
95  			}
96  
97  			@Override
98  			protected File discoverGitExe() {
99  				return null;
100 			}
101 
102 			@Override
103 			public boolean canExecute(File f) {
104 				return true;
105 			}
106 
107 			@Override
108 			public boolean isCaseSensitive() {
109 				return true;
110 			}
111 		};
112 
113 		Git git = Git.open(db.getDirectory(), executableFs);
114 		String path = "a.txt";
115 		writeTrashFile(path, "content");
116 		git.add().addFilepattern(path).call();
117 		RevCommit commit1 = git.commit().setMessage("commit").call();
118 		try (TreeWalk walk = TreeWalk.forPath(db, path, commit1.getTree())) {
119 			assertNotNull(walk);
120 			assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
121 		}
122 
123 		FS nonExecutableFs = new FS() {
124 
125 			@Override
126 			public boolean supportsExecute() {
127 				return false;
128 			}
129 
130 			@Override
131 			public boolean setExecute(File f, boolean canExec) {
132 				return false;
133 			}
134 
135 			@Override
136 			public ProcessBuilder runInShell(String cmd, String[] args) {
137 				return null;
138 			}
139 
140 			@Override
141 			public boolean retryFailedLockFileCommit() {
142 				return false;
143 			}
144 
145 			@Override
146 			public FS newInstance() {
147 				return this;
148 			}
149 
150 			@Override
151 			protected File discoverGitExe() {
152 				return null;
153 			}
154 
155 			@Override
156 			public boolean canExecute(File f) {
157 				return false;
158 			}
159 
160 			@Override
161 			public boolean isCaseSensitive() {
162 				return true;
163 			}
164 		};
165 
166 		config = db.getConfig();
167 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
168 				ConfigConstants.CONFIG_KEY_FILEMODE, false);
169 		config.save();
170 
171 		Git git2 = Git.open(db.getDirectory(), nonExecutableFs);
172 		writeTrashFile(path, "content2");
173 		RevCommit commit2 = git2.commit().setOnly(path).setMessage("commit2")
174 				.call();
175 		try (TreeWalk walk = TreeWalk.forPath(db, path, commit2.getTree())) {
176 			assertNotNull(walk);
177 			assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
178 		}
179 	}
180 
181 	@Test
182 	public void commitNewSubmodule() throws Exception {
183 		try (Git git = new Git(db)) {
184 			writeTrashFile("file.txt", "content");
185 			git.add().addFilepattern("file.txt").call();
186 			RevCommit commit = git.commit().setMessage("create file").call();
187 
188 			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
189 			String path = "sub";
190 			command.setPath(path);
191 			String uri = db.getDirectory().toURI().toString();
192 			command.setURI(uri);
193 			Repository repo = command.call();
194 			assertNotNull(repo);
195 			addRepoToClose(repo);
196 
197 			try (SubmoduleWalk generator = SubmoduleWalk.forIndex(db)) {
198 				assertTrue(generator.next());
199 				assertEquals(path, generator.getPath());
200 				assertEquals(commit, generator.getObjectId());
201 				assertEquals(uri, generator.getModulesUrl());
202 				assertEquals(path, generator.getModulesPath());
203 				assertEquals(uri, generator.getConfigUrl());
204 				try (Repository subModRepo = generator.getRepository()) {
205 					assertNotNull(subModRepo);
206 				}
207 			}
208 			assertEquals(commit, repo.resolve(Constants.HEAD));
209 
210 			RevCommit submoduleCommit = git.commit().setMessage("submodule add")
211 					.setOnly(path).call();
212 			assertNotNull(submoduleCommit);
213 			try (TreeWalk walk = new TreeWalk(db)) {
214 				walk.addTree(commit.getTree());
215 				walk.addTree(submoduleCommit.getTree());
216 				walk.setFilter(TreeFilter.ANY_DIFF);
217 				List<DiffEntry> diffs = DiffEntry.scan(walk);
218 				assertEquals(1, diffs.size());
219 				DiffEntry subDiff = diffs.get(0);
220 				assertEquals(FileMode.MISSING, subDiff.getOldMode());
221 				assertEquals(FileMode.GITLINK, subDiff.getNewMode());
222 				assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId());
223 				assertEquals(commit, subDiff.getNewId().toObjectId());
224 				assertEquals(path, subDiff.getNewPath());
225 			}
226 		}
227 	}
228 
229 	@Test
230 	public void commitSubmoduleUpdate() throws Exception {
231 		try (Git git = new Git(db)) {
232 			writeTrashFile("file.txt", "content");
233 			git.add().addFilepattern("file.txt").call();
234 			RevCommit commit = git.commit().setMessage("create file").call();
235 			writeTrashFile("file.txt", "content2");
236 			git.add().addFilepattern("file.txt").call();
237 			RevCommit commit2 = git.commit().setMessage("edit file").call();
238 
239 			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
240 			String path = "sub";
241 			command.setPath(path);
242 			String uri = db.getDirectory().toURI().toString();
243 			command.setURI(uri);
244 			Repository repo = command.call();
245 			assertNotNull(repo);
246 			addRepoToClose(repo);
247 
248 			try (SubmoduleWalk generator = SubmoduleWalk.forIndex(db)) {
249 				assertTrue(generator.next());
250 				assertEquals(path, generator.getPath());
251 				assertEquals(commit2, generator.getObjectId());
252 				assertEquals(uri, generator.getModulesUrl());
253 				assertEquals(path, generator.getModulesPath());
254 				assertEquals(uri, generator.getConfigUrl());
255 				try (Repository subModRepo = generator.getRepository()) {
256 					assertNotNull(subModRepo);
257 				}
258 			}
259 			assertEquals(commit2, repo.resolve(Constants.HEAD));
260 
261 			RevCommit submoduleAddCommit = git.commit().setMessage("submodule add")
262 					.setOnly(path).call();
263 			assertNotNull(submoduleAddCommit);
264 
265 			RefUpdate update = repo.updateRef(Constants.HEAD);
266 			update.setNewObjectId(commit);
267 			assertEquals(Result.FORCED, update.forceUpdate());
268 
269 			RevCommit submoduleEditCommit = git.commit()
270 					.setMessage("submodule add").setOnly(path).call();
271 			assertNotNull(submoduleEditCommit);
272 			try (TreeWalk walk = new TreeWalk(db)) {
273 				walk.addTree(submoduleAddCommit.getTree());
274 				walk.addTree(submoduleEditCommit.getTree());
275 				walk.setFilter(TreeFilter.ANY_DIFF);
276 				List<DiffEntry> diffs = DiffEntry.scan(walk);
277 				assertEquals(1, diffs.size());
278 				DiffEntry subDiff = diffs.get(0);
279 				assertEquals(FileMode.GITLINK, subDiff.getOldMode());
280 				assertEquals(FileMode.GITLINK, subDiff.getNewMode());
281 				assertEquals(commit2, subDiff.getOldId().toObjectId());
282 				assertEquals(commit, subDiff.getNewId().toObjectId());
283 				assertEquals(path, subDiff.getNewPath());
284 				assertEquals(path, subDiff.getOldPath());
285 			}
286 		}
287 	}
288 
289 	@Ignore("very flaky when run with Hudson")
290 	@Test
291 	public void commitUpdatesSmudgedEntries() throws Exception {
292 		try (Git git = new Git(db)) {
293 			File file1 = writeTrashFile("file1.txt", "content1");
294 			TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
295 			File file2 = writeTrashFile("file2.txt", "content2");
296 			TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
297 			File file3 = writeTrashFile("file3.txt", "content3");
298 			TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L);
299 
300 			assertNotNull(git.add().addFilepattern("file1.txt")
301 					.addFilepattern("file2.txt").addFilepattern("file3.txt").call());
302 			RevCommit commit = git.commit().setMessage("add files").call();
303 			assertNotNull(commit);
304 
305 			DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
306 			int file1Size = cache.getEntry("file1.txt").getLength();
307 			int file2Size = cache.getEntry("file2.txt").getLength();
308 			int file3Size = cache.getEntry("file3.txt").getLength();
309 			ObjectId file2Id = cache.getEntry("file2.txt").getObjectId();
310 			ObjectId file3Id = cache.getEntry("file3.txt").getObjectId();
311 			assertTrue(file1Size > 0);
312 			assertTrue(file2Size > 0);
313 			assertTrue(file3Size > 0);
314 
315 			// Smudge entries
316 			cache = DirCache.lock(db.getIndexFile(), db.getFS());
317 			cache.getEntry("file1.txt").setLength(0);
318 			cache.getEntry("file2.txt").setLength(0);
319 			cache.getEntry("file3.txt").setLength(0);
320 			cache.write();
321 			assertTrue(cache.commit());
322 
323 			// Verify entries smudged
324 			cache = DirCache.read(db.getIndexFile(), db.getFS());
325 			assertEquals(0, cache.getEntry("file1.txt").getLength());
326 			assertEquals(0, cache.getEntry("file2.txt").getLength());
327 			assertEquals(0, cache.getEntry("file3.txt").getLength());
328 
329 			TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
330 					-5000L);
331 
332 			write(file1, "content4");
333 
334 			TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L);
335 			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
336 					.call());
337 
338 			cache = db.readDirCache();
339 			assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
340 			assertEquals(file2Size, cache.getEntry("file2.txt").getLength());
341 			assertEquals(file3Size, cache.getEntry("file3.txt").getLength());
342 			assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId());
343 			assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId());
344 		}
345 	}
346 
347 	@Ignore("very flaky when run with Hudson")
348 	@Test
349 	public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
350 		try (Git git = new Git(db)) {
351 			File file1 = writeTrashFile("file1.txt", "content1");
352 			TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
353 			File file2 = writeTrashFile("file2.txt", "content2");
354 			TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
355 
356 			assertNotNull(git.add().addFilepattern("file1.txt")
357 					.addFilepattern("file2.txt").call());
358 			RevCommit commit = git.commit().setMessage("add files").call();
359 			assertNotNull(commit);
360 
361 			DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
362 			int file1Size = cache.getEntry("file1.txt").getLength();
363 			int file2Size = cache.getEntry("file2.txt").getLength();
364 			assertTrue(file1Size > 0);
365 			assertTrue(file2Size > 0);
366 
367 			writeTrashFile("file2.txt", "content3");
368 			assertNotNull(git.add().addFilepattern("file2.txt").call());
369 			writeTrashFile("file2.txt", "content4");
370 
371 			// Smudge entries
372 			cache = DirCache.lock(db.getIndexFile(), db.getFS());
373 			cache.getEntry("file1.txt").setLength(0);
374 			cache.getEntry("file2.txt").setLength(0);
375 			cache.write();
376 			assertTrue(cache.commit());
377 
378 			// Verify entries smudged
379 			cache = db.readDirCache();
380 			assertEquals(0, cache.getEntry("file1.txt").getLength());
381 			assertEquals(0, cache.getEntry("file2.txt").getLength());
382 
383 			TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
384 					-5000L);
385 
386 			write(file1, "content5");
387 			TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L);
388 
389 			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
390 					.call());
391 
392 			cache = db.readDirCache();
393 			assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
394 			assertEquals(0, cache.getEntry("file2.txt").getLength());
395 		}
396 	}
397 
398 	@Test
399 	public void commitAfterSquashMerge() throws Exception {
400 		try (Git git = new Git(db)) {
401 			writeTrashFile("file1", "file1");
402 			git.add().addFilepattern("file1").call();
403 			RevCommit first = git.commit().setMessage("initial commit").call();
404 
405 			assertTrue(new File(db.getWorkTree(), "file1").exists());
406 			createBranch(first, "refs/heads/branch1");
407 			checkoutBranch("refs/heads/branch1");
408 
409 			writeTrashFile("file2", "file2");
410 			git.add().addFilepattern("file2").call();
411 			git.commit().setMessage("second commit").call();
412 			assertTrue(new File(db.getWorkTree(), "file2").exists());
413 
414 			checkoutBranch("refs/heads/master");
415 
416 			MergeResult result = git.merge()
417 					.include(db.exactRef("refs/heads/branch1"))
418 					.setSquash(true)
419 					.call();
420 
421 			assertTrue(new File(db.getWorkTree(), "file1").exists());
422 			assertTrue(new File(db.getWorkTree(), "file2").exists());
423 			assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
424 					result.getMergeStatus());
425 
426 			// comment not set, should be inferred from SQUASH_MSG
427 			RevCommit squashedCommit = git.commit().call();
428 
429 			assertEquals(1, squashedCommit.getParentCount());
430 			assertNull(db.readSquashCommitMsg());
431 			assertEquals("commit: Squashed commit of the following:", db
432 					.getReflogReader(Constants.HEAD).getLastEntry().getComment());
433 			assertEquals("commit: Squashed commit of the following:", db
434 					.getReflogReader(db.getBranch()).getLastEntry().getComment());
435 		}
436 	}
437 
438 	@Test
439 	public void testReflogs() throws Exception {
440 		try (Git git = new Git(db)) {
441 			writeTrashFile("f", "1");
442 			git.add().addFilepattern("f").call();
443 			git.commit().setMessage("c1").call();
444 			writeTrashFile("f", "2");
445 			git.commit().setMessage("c2").setAll(true).setReflogComment(null)
446 					.call();
447 			writeTrashFile("f", "3");
448 			git.commit().setMessage("c3").setAll(true)
449 					.setReflogComment("testRl").call();
450 
451 			db.getReflogReader(Constants.HEAD).getReverseEntries();
452 
453 			assertEquals("testRl;commit (initial): c1;", reflogComments(
454 					db.getReflogReader(Constants.HEAD).getReverseEntries()));
455 			assertEquals("testRl;commit (initial): c1;", reflogComments(
456 					db.getReflogReader(db.getBranch()).getReverseEntries()));
457 		}
458 	}
459 
460 	private static String reflogComments(List<ReflogEntry> entries) {
461 		StringBuilder b = new StringBuilder();
462 		for (ReflogEntry e : entries) {
463 			b.append(e.getComment()).append(";");
464 		}
465 		return b.toString();
466 	}
467 
468 	@Test(expected = WrongRepositoryStateException.class)
469 	public void commitAmendOnInitialShouldFail() throws Exception {
470 		try (Git git = new Git(db)) {
471 			git.commit().setAmend(true).setMessage("initial commit").call();
472 		}
473 	}
474 
475 	@Test
476 	public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime()
477 			throws Exception {
478 		try (Git git = new Git(db)) {
479 			writeTrashFile("file1", "file1");
480 			git.add().addFilepattern("file1").call();
481 
482 			final String authorName = "First Author";
483 			final String authorEmail = "author@example.org";
484 			final Date authorDate = new Date(1349621117000L);
485 			PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail,
486 					authorDate, TimeZone.getTimeZone("UTC"));
487 			git.commit().setMessage("initial commit").setAuthor(firstAuthor).call();
488 
489 			RevCommit amended = git.commit().setAmend(true)
490 					.setMessage("amend commit").call();
491 
492 			PersonIdent amendedAuthor = amended.getAuthorIdent();
493 			assertEquals(authorName, amendedAuthor.getName());
494 			assertEquals(authorEmail, amendedAuthor.getEmailAddress());
495 			assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime());
496 		}
497 	}
498 
499 	@Test
500 	public void commitAmendWithAuthorShouldUseIt() throws Exception {
501 		try (Git git = new Git(db)) {
502 			writeTrashFile("file1", "file1");
503 			git.add().addFilepattern("file1").call();
504 			git.commit().setMessage("initial commit").call();
505 
506 			RevCommit amended = git.commit().setAmend(true)
507 					.setAuthor("New Author", "newauthor@example.org")
508 					.setMessage("amend commit").call();
509 
510 			PersonIdent amendedAuthor = amended.getAuthorIdent();
511 			assertEquals("New Author", amendedAuthor.getName());
512 			assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress());
513 		}
514 	}
515 
516 	@Test
517 	public void commitEmptyCommits() throws Exception {
518 		try (Git git = new Git(db)) {
519 
520 			writeTrashFile("file1", "file1");
521 			git.add().addFilepattern("file1").call();
522 			RevCommit initial = git.commit().setMessage("initial commit")
523 					.call();
524 
525 			RevCommit emptyFollowUp = git.commit()
526 					.setAuthor("New Author", "newauthor@example.org")
527 					.setMessage("no change").call();
528 
529 			assertNotEquals(initial.getId(), emptyFollowUp.getId());
530 			assertEquals(initial.getTree().getId(),
531 					emptyFollowUp.getTree().getId());
532 
533 			try {
534 				git.commit().setAuthor("New Author", "newauthor@example.org")
535 						.setMessage("again no change").setAllowEmpty(false)
536 						.call();
537 				fail("Didn't get the expected EmptyCommitException");
538 			} catch (EmptyCommitException e) {
539 				// expect this exception
540 			}
541 
542 			// Allow empty commits also when setOnly was set
543 			git.commit().setAuthor("New Author", "newauthor@example.org")
544 					.setMessage("again no change").setOnly("file1")
545 					.setAllowEmpty(true).call();
546 		}
547 	}
548 
549 	@Test
550 	public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers()
551 			throws Exception {
552 		DirCache index = db.lockDirCache();
553 		DirCacheBuilder builder = index.builder();
554 		addUnmergedEntry("unmerged1", builder);
555 		addUnmergedEntry("unmerged2", builder);
556 		DirCacheEntry other = new DirCacheEntry("other");
557 		other.setFileMode(FileMode.REGULAR_FILE);
558 		builder.add(other);
559 		builder.commit();
560 
561 		writeTrashFile("unmerged1", "unmerged1 data");
562 		writeTrashFile("unmerged2", "unmerged2 data");
563 		writeTrashFile("other", "other data");
564 
565 		assertEquals("[other, mode:100644]"
566 				+ "[unmerged1, mode:100644, stage:1]"
567 				+ "[unmerged1, mode:100644, stage:2]"
568 				+ "[unmerged1, mode:100644, stage:3]"
569 				+ "[unmerged2, mode:100644, stage:1]"
570 				+ "[unmerged2, mode:100644, stage:2]"
571 				+ "[unmerged2, mode:100644, stage:3]",
572 				indexState(0));
573 
574 		try (Git git = new Git(db)) {
575 			RevCommit commit = git.commit().setOnly("unmerged1")
576 					.setMessage("Only one file").call();
577 
578 			assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]"
579 					+ "[unmerged2, mode:100644, stage:1]"
580 					+ "[unmerged2, mode:100644, stage:2]"
581 					+ "[unmerged2, mode:100644, stage:3]",
582 					indexState(0));
583 
584 			try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) {
585 				assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
586 			}
587 		}
588 	}
589 
590 	@Test
591 	public void commitOnlyShouldHandleIgnored() throws Exception {
592 		try (Git git = new Git(db)) {
593 			writeTrashFile("subdir/foo", "Hello World");
594 			writeTrashFile("subdir/bar", "Hello World");
595 			writeTrashFile(".gitignore", "bar");
596 			git.add().addFilepattern("subdir").call();
597 			git.commit().setOnly("subdir").setMessage("first commit").call();
598 			assertEquals("[subdir/foo, mode:100644, content:Hello World]",
599 					indexState(CONTENT));
600 		}
601 	}
602 
603 	@Test
604 	public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception {
605 		try (Git git = new Git(db)) {
606 			// Commit a file with CR/LF into the index
607 			FileBasedConfig config = db.getConfig();
608 			config.setString("core", null, "autocrlf", "false");
609 			config.save();
610 			writeTrashFile("file.txt", "line 1\r\nline 2\r\n");
611 			git.add().addFilepattern("file.txt").call();
612 			git.commit().setMessage("Initial").call();
613 			assertEquals(
614 					"[file.txt, mode:100644, content:line 1\r\nline 2\r\n]",
615 					indexState(CONTENT));
616 			config.setString("core", null, "autocrlf", "true");
617 			config.save();
618 			writeTrashFile("file.txt", "line 1\r\nline 1.5\r\nline 2\r\n");
619 			writeTrashFile("file2.txt", "new\r\nfile\r\n");
620 			git.add().addFilepattern("file.txt").addFilepattern("file2.txt")
621 					.call();
622 			git.commit().setMessage("Second").call();
623 			assertEquals(
624 					"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]"
625 							+ "[file2.txt, mode:100644, content:new\nfile\n]",
626 					indexState(CONTENT));
627 			writeTrashFile("file2.txt", "new\r\nfile\r\ncontent\r\n");
628 			git.add().addFilepattern("file2.txt").call();
629 			git.commit().setMessage("Third").call();
630 			assertEquals(
631 					"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]"
632 							+ "[file2.txt, mode:100644, content:new\nfile\ncontent\n]",
633 					indexState(CONTENT));
634 		}
635 	}
636 
637 	@Test
638 	public void testDeletionConflictWithAutoCrlf() throws Exception {
639 		try (Git git = new Git(db)) {
640 			// Commit a file with CR/LF into the index
641 			FileBasedConfig config = db.getConfig();
642 			config.setString("core", null, "autocrlf", "false");
643 			config.save();
644 			File file = writeTrashFile("file.txt", "foo\r\n");
645 			git.add().addFilepattern("file.txt").call();
646 			git.commit().setMessage("Initial").call();
647 			// Switch to side branch
648 			git.checkout().setCreateBranch(true).setName("side").call();
649 			assertTrue(file.delete());
650 			git.rm().addFilepattern("file.txt").call();
651 			git.commit().setMessage("Side").call();
652 			// Switch on autocrlf=true
653 			config.setString("core", null, "autocrlf", "true");
654 			config.save();
655 			// Switch back to master and commit a conflict
656 			git.checkout().setName("master").call();
657 			writeTrashFile("file.txt", "foob\r\n");
658 			git.add().addFilepattern("file.txt").call();
659 			assertEquals("[file.txt, mode:100644, content:foob\r\n]",
660 					indexState(CONTENT));
661 			writeTrashFile("g", "file2.txt", "anything");
662 			git.add().addFilepattern("g/file2.txt");
663 			RevCommit master = git.commit().setMessage("Second").call();
664 			// Switch to side branch again so that the deletion is "ours"
665 			git.checkout().setName("side").call();
666 			// Cherry pick master: produces a delete-modify conflict.
667 			CherryPickResult pick = git.cherryPick().include(master).call();
668 			assertEquals("Expected a cherry-pick conflict",
669 					CherryPickStatus.CONFLICTING, pick.getStatus());
670 			// XXX: g/file2.txt should actually be staged already, but isn't.
671 			git.add().addFilepattern("g/file2.txt").call();
672 			// Resolve the conflict by taking the master version
673 			writeTrashFile("file.txt", "foob\r\n");
674 			git.add().addFilepattern("file.txt").call();
675 			git.commit().setMessage("Cherry").call();
676 			// We expect this to be committed with a single LF since there is no
677 			// "ours" stage.
678 			assertEquals(
679 					"[file.txt, mode:100644, content:foob\n]"
680 							+ "[g/file2.txt, mode:100644, content:anything]",
681 					indexState(CONTENT));
682 		}
683 	}
684 
685 	private void testConflictWithAutoCrlf(String baseLf, String lf)
686 			throws Exception {
687 		try (Git git = new Git(db)) {
688 			// Commit a file with CR/LF into the index
689 			FileBasedConfig config = db.getConfig();
690 			config.setString("core", null, "autocrlf", "false");
691 			config.save();
692 			writeTrashFile("file.txt", "foo" + baseLf);
693 			git.add().addFilepattern("file.txt").call();
694 			git.commit().setMessage("Initial").call();
695 			// Switch to side branch
696 			git.checkout().setCreateBranch(true).setName("side").call();
697 			writeTrashFile("file.txt", "bar\r\n");
698 			git.add().addFilepattern("file.txt").call();
699 			RevCommit side = git.commit().setMessage("Side").call();
700 			// Switch back to master and commit a conflict with the given lf
701 			git.checkout().setName("master");
702 			writeTrashFile("file.txt", "foob" + lf);
703 			git.add().addFilepattern("file.txt").call();
704 			git.commit().setMessage("Second").call();
705 			// Switch on autocrlf=true
706 			config.setString("core", null, "autocrlf", "true");
707 			config.save();
708 			// Cherry pick side: conflict. Resolve with CR-LF and commit.
709 			CherryPickResult pick = git.cherryPick().include(side).call();
710 			assertEquals("Expected a cherry-pick conflict",
711 					CherryPickStatus.CONFLICTING, pick.getStatus());
712 			writeTrashFile("file.txt", "foobar\r\n");
713 			git.add().addFilepattern("file.txt").call();
714 			git.commit().setMessage("Second").call();
715 			assertEquals("[file.txt, mode:100644, content:foobar" + lf + "]",
716 					indexState(CONTENT));
717 		}
718 	}
719 
720 	@Test
721 	public void commitConflictWithAutoCrlfBaseCrLfOursLf() throws Exception {
722 		testConflictWithAutoCrlf("\r\n", "\n");
723 	}
724 
725 	@Test
726 	public void commitConflictWithAutoCrlfBaseLfOursLf() throws Exception {
727 		testConflictWithAutoCrlf("\n", "\n");
728 	}
729 
730 	@Test
731 	public void commitConflictWithAutoCrlfBasCrLfOursCrLf() throws Exception {
732 		testConflictWithAutoCrlf("\r\n", "\r\n");
733 	}
734 
735 	@Test
736 	public void commitConflictWithAutoCrlfBaseLfOursCrLf() throws Exception {
737 		testConflictWithAutoCrlf("\n", "\r\n");
738 	}
739 
740 	private static void addUnmergedEntry(String file, DirCacheBuilder builder) {
741 		DirCacheEntry stage1 = new DirCacheEntry(file, DirCacheEntry.STAGE_1);
742 		DirCacheEntry stage2 = new DirCacheEntry(file, DirCacheEntry.STAGE_2);
743 		DirCacheEntry stage3 = new DirCacheEntry(file, DirCacheEntry.STAGE_3);
744 		stage1.setFileMode(FileMode.REGULAR_FILE);
745 		stage2.setFileMode(FileMode.REGULAR_FILE);
746 		stage3.setFileMode(FileMode.REGULAR_FILE);
747 		builder.add(stage1);
748 		builder.add(stage2);
749 		builder.add(stage3);
750 	}
751 
752 	@Test
753 	public void callSignerWithProperSigningKey() throws Exception {
754 		try (Git git = new Git(db)) {
755 			writeTrashFile("file1", "file1");
756 			git.add().addFilepattern("file1").call();
757 
758 			String[] signingKey = new String[1];
759 			PersonIdent[] signingCommitters = new PersonIdent[1];
760 			AtomicInteger callCount = new AtomicInteger();
761 			GpgSigner.setDefault(new GpgSigner() {
762 				@Override
763 				public void sign(CommitBuilder commit, String gpgSigningKey,
764 						PersonIdent signingCommitter, CredentialsProvider credentialsProvider) {
765 					signingKey[0] = gpgSigningKey;
766 					signingCommitters[0] = signingCommitter;
767 					callCount.incrementAndGet();
768 				}
769 
770 				@Override
771 				public boolean canLocateSigningKey(String gpgSigningKey,
772 						PersonIdent signingCommitter,
773 						CredentialsProvider credentialsProvider)
774 						throws CanceledException {
775 					return false;
776 				}
777 			});
778 
779 			// first call should use config, which is expected to be null at
780 			// this time
781 			git.commit().setCommitter(committer).setSign(Boolean.TRUE)
782 					.setMessage("initial commit")
783 					.call();
784 			assertNull(signingKey[0]);
785 			assertEquals(1, callCount.get());
786 			assertSame(committer, signingCommitters[0]);
787 
788 			writeTrashFile("file2", "file2");
789 			git.add().addFilepattern("file2").call();
790 
791 			// second commit applies config value
792 			String expectedConfigSigningKey = "config-" + System.nanoTime();
793 			StoredConfig config = git.getRepository().getConfig();
794 			config.setString("user", null, "signingKey",
795 					expectedConfigSigningKey);
796 			config.save();
797 
798 			git.commit().setCommitter(committer).setSign(Boolean.TRUE)
799 					.setMessage("initial commit")
800 					.call();
801 			assertEquals(expectedConfigSigningKey, signingKey[0]);
802 			assertEquals(2, callCount.get());
803 			assertSame(committer, signingCommitters[0]);
804 
805 			writeTrashFile("file3", "file3");
806 			git.add().addFilepattern("file3").call();
807 
808 			// now use specific on api
809 			String expectedSigningKey = "my-" + System.nanoTime();
810 			git.commit().setCommitter(committer).setSign(Boolean.TRUE)
811 					.setSigningKey(expectedSigningKey)
812 					.setMessage("initial commit").call();
813 			assertEquals(expectedSigningKey, signingKey[0]);
814 			assertEquals(3, callCount.get());
815 			assertSame(committer, signingCommitters[0]);
816 		}
817 	}
818 
819 	@Test
820 	public void callSignerOnlyWhenSigning() throws Exception {
821 		try (Git git = new Git(db)) {
822 			writeTrashFile("file1", "file1");
823 			git.add().addFilepattern("file1").call();
824 
825 			AtomicInteger callCount = new AtomicInteger();
826 			GpgSigner.setDefault(new GpgSigner() {
827 				@Override
828 				public void sign(CommitBuilder commit, String gpgSigningKey,
829 						PersonIdent signingCommitter, CredentialsProvider credentialsProvider) {
830 					callCount.incrementAndGet();
831 				}
832 
833 				@Override
834 				public boolean canLocateSigningKey(String gpgSigningKey,
835 						PersonIdent signingCommitter,
836 						CredentialsProvider credentialsProvider)
837 						throws CanceledException {
838 					return false;
839 				}
840 			});
841 
842 			// first call should use config, which is expected to be null at
843 			// this time
844 			git.commit().setMessage("initial commit").call();
845 			assertEquals(0, callCount.get());
846 
847 			writeTrashFile("file2", "file2");
848 			git.add().addFilepattern("file2").call();
849 
850 			// now force signing
851 			git.commit().setSign(Boolean.TRUE).setMessage("commit").call();
852 			assertEquals(1, callCount.get());
853 
854 			writeTrashFile("file3", "file3");
855 			git.add().addFilepattern("file3").call();
856 
857 			// now rely on config
858 			StoredConfig config = git.getRepository().getConfig();
859 			config.setBoolean("commit", null, "gpgSign", true);
860 			config.save();
861 
862 			git.commit().setMessage("commit").call();
863 			assertEquals(2, callCount.get());
864 
865 			writeTrashFile("file4", "file4");
866 			git.add().addFilepattern("file4").call();
867 
868 			// now force "no-sign" (even though config is true)
869 			git.commit().setSign(Boolean.FALSE).setMessage("commit").call();
870 			assertEquals(2, callCount.get());
871 		}
872 	}
873 }