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