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