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