1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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.assertTrue;
50 import static org.junit.Assert.fail;
51
52 import java.io.File;
53 import java.util.Date;
54 import java.util.List;
55 import java.util.TimeZone;
56
57 import org.eclipse.jgit.api.errors.EmtpyCommitException;
58 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
59 import org.eclipse.jgit.diff.DiffEntry;
60 import org.eclipse.jgit.dircache.DirCache;
61 import org.eclipse.jgit.dircache.DirCacheBuilder;
62 import org.eclipse.jgit.dircache.DirCacheEntry;
63 import org.eclipse.jgit.junit.RepositoryTestCase;
64 import org.eclipse.jgit.lib.ConfigConstants;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.FileMode;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.PersonIdent;
69 import org.eclipse.jgit.lib.RefUpdate;
70 import org.eclipse.jgit.lib.RefUpdate.Result;
71 import org.eclipse.jgit.lib.ReflogEntry;
72 import org.eclipse.jgit.lib.Repository;
73 import org.eclipse.jgit.lib.StoredConfig;
74 import org.eclipse.jgit.revwalk.RevCommit;
75 import org.eclipse.jgit.submodule.SubmoduleWalk;
76 import org.eclipse.jgit.treewalk.TreeWalk;
77 import org.eclipse.jgit.treewalk.filter.TreeFilter;
78 import org.eclipse.jgit.util.FS;
79 import org.junit.Ignore;
80 import org.junit.Test;
81
82
83
84
85 public class CommitCommandTest extends RepositoryTestCase {
86
87 @Test
88 public void testExecutableRetention() throws Exception {
89 StoredConfig config = db.getConfig();
90 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
91 ConfigConstants.CONFIG_KEY_FILEMODE, true);
92 config.save();
93
94 FS executableFs = new FS() {
95
96 @Override
97 public boolean supportsExecute() {
98 return true;
99 }
100
101 @Override
102 public boolean setExecute(File f, boolean canExec) {
103 return true;
104 }
105
106 @Override
107 public ProcessBuilder runInShell(String cmd, String[] args) {
108 return null;
109 }
110
111 @Override
112 public boolean retryFailedLockFileCommit() {
113 return false;
114 }
115
116 @Override
117 public FS newInstance() {
118 return this;
119 }
120
121 @Override
122 protected File discoverGitExe() {
123 return null;
124 }
125
126 @Override
127 public boolean canExecute(File f) {
128 return true;
129 }
130
131 @Override
132 public boolean isCaseSensitive() {
133 return true;
134 }
135 };
136
137 Git git = Git.open(db.getDirectory(), executableFs);
138 String path = "a.txt";
139 writeTrashFile(path, "content");
140 git.add().addFilepattern(path).call();
141 RevCommit commit1 = git.commit().setMessage("commit").call();
142 TreeWalk walk = TreeWalk.forPath(db, path, commit1.getTree());
143 assertNotNull(walk);
144 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
145
146 FS nonExecutableFs = new FS() {
147
148 @Override
149 public boolean supportsExecute() {
150 return false;
151 }
152
153 @Override
154 public boolean setExecute(File f, boolean canExec) {
155 return false;
156 }
157
158 @Override
159 public ProcessBuilder runInShell(String cmd, String[] args) {
160 return null;
161 }
162
163 @Override
164 public boolean retryFailedLockFileCommit() {
165 return false;
166 }
167
168 @Override
169 public FS newInstance() {
170 return this;
171 }
172
173 @Override
174 protected File discoverGitExe() {
175 return null;
176 }
177
178 @Override
179 public boolean canExecute(File f) {
180 return false;
181 }
182
183 @Override
184 public boolean isCaseSensitive() {
185 return true;
186 }
187 };
188
189 config = db.getConfig();
190 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
191 ConfigConstants.CONFIG_KEY_FILEMODE, false);
192 config.save();
193
194 Git git2 = Git.open(db.getDirectory(), nonExecutableFs);
195 writeTrashFile(path, "content2");
196 RevCommit commit2 = git2.commit().setOnly(path).setMessage("commit2")
197 .call();
198 walk = TreeWalk.forPath(db, path, commit2.getTree());
199 assertNotNull(walk);
200 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
201 }
202
203 @Test
204 public void commitNewSubmodule() throws Exception {
205 try (Git git = new Git(db)) {
206 writeTrashFile("file.txt", "content");
207 git.add().addFilepattern("file.txt").call();
208 RevCommit commit = git.commit().setMessage("create file").call();
209
210 SubmoduleAddCommand command = new SubmoduleAddCommand(db);
211 String path = "sub";
212 command.setPath(path);
213 String uri = db.getDirectory().toURI().toString();
214 command.setURI(uri);
215 Repository repo = command.call();
216 assertNotNull(repo);
217 addRepoToClose(repo);
218
219 SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
220 assertTrue(generator.next());
221 assertEquals(path, generator.getPath());
222 assertEquals(commit, generator.getObjectId());
223 assertEquals(uri, generator.getModulesUrl());
224 assertEquals(path, generator.getModulesPath());
225 assertEquals(uri, generator.getConfigUrl());
226 try (Repository subModRepo = generator.getRepository()) {
227 assertNotNull(subModRepo);
228 }
229 assertEquals(commit, repo.resolve(Constants.HEAD));
230
231 RevCommit submoduleCommit = git.commit().setMessage("submodule add")
232 .setOnly(path).call();
233 assertNotNull(submoduleCommit);
234 try (TreeWalk walk = new TreeWalk(db)) {
235 walk.addTree(commit.getTree());
236 walk.addTree(submoduleCommit.getTree());
237 walk.setFilter(TreeFilter.ANY_DIFF);
238 List<DiffEntry> diffs = DiffEntry.scan(walk);
239 assertEquals(1, diffs.size());
240 DiffEntry subDiff = diffs.get(0);
241 assertEquals(FileMode.MISSING, subDiff.getOldMode());
242 assertEquals(FileMode.GITLINK, subDiff.getNewMode());
243 assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId());
244 assertEquals(commit, subDiff.getNewId().toObjectId());
245 assertEquals(path, subDiff.getNewPath());
246 }
247 }
248 }
249
250 @Test
251 public void commitSubmoduleUpdate() throws Exception {
252 try (Git git = new Git(db)) {
253 writeTrashFile("file.txt", "content");
254 git.add().addFilepattern("file.txt").call();
255 RevCommit commit = git.commit().setMessage("create file").call();
256 writeTrashFile("file.txt", "content2");
257 git.add().addFilepattern("file.txt").call();
258 RevCommit commit2 = git.commit().setMessage("edit file").call();
259
260 SubmoduleAddCommand command = new SubmoduleAddCommand(db);
261 String path = "sub";
262 command.setPath(path);
263 String uri = db.getDirectory().toURI().toString();
264 command.setURI(uri);
265 Repository repo = command.call();
266 assertNotNull(repo);
267 addRepoToClose(repo);
268
269 SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
270 assertTrue(generator.next());
271 assertEquals(path, generator.getPath());
272 assertEquals(commit2, generator.getObjectId());
273 assertEquals(uri, generator.getModulesUrl());
274 assertEquals(path, generator.getModulesPath());
275 assertEquals(uri, generator.getConfigUrl());
276 try (Repository subModRepo = generator.getRepository()) {
277 assertNotNull(subModRepo);
278 }
279 assertEquals(commit2, repo.resolve(Constants.HEAD));
280
281 RevCommit submoduleAddCommit = git.commit().setMessage("submodule add")
282 .setOnly(path).call();
283 assertNotNull(submoduleAddCommit);
284
285 RefUpdate update = repo.updateRef(Constants.HEAD);
286 update.setNewObjectId(commit);
287 assertEquals(Result.FORCED, update.forceUpdate());
288
289 RevCommit submoduleEditCommit = git.commit()
290 .setMessage("submodule add").setOnly(path).call();
291 assertNotNull(submoduleEditCommit);
292 try (TreeWalk walk = new TreeWalk(db)) {
293 walk.addTree(submoduleAddCommit.getTree());
294 walk.addTree(submoduleEditCommit.getTree());
295 walk.setFilter(TreeFilter.ANY_DIFF);
296 List<DiffEntry> diffs = DiffEntry.scan(walk);
297 assertEquals(1, diffs.size());
298 DiffEntry subDiff = diffs.get(0);
299 assertEquals(FileMode.GITLINK, subDiff.getOldMode());
300 assertEquals(FileMode.GITLINK, subDiff.getNewMode());
301 assertEquals(commit2, subDiff.getOldId().toObjectId());
302 assertEquals(commit, subDiff.getNewId().toObjectId());
303 assertEquals(path, subDiff.getNewPath());
304 assertEquals(path, subDiff.getOldPath());
305 }
306 }
307 }
308
309 @Ignore("very flaky when run with Hudson")
310 @Test
311 public void commitUpdatesSmudgedEntries() throws Exception {
312 try (Git git = new Git(db)) {
313 File file1 = writeTrashFile("file1.txt", "content1");
314 assertTrue(file1.setLastModified(file1.lastModified() - 5000));
315 File file2 = writeTrashFile("file2.txt", "content2");
316 assertTrue(file2.setLastModified(file2.lastModified() - 5000));
317 File file3 = writeTrashFile("file3.txt", "content3");
318 assertTrue(file3.setLastModified(file3.lastModified() - 5000));
319
320 assertNotNull(git.add().addFilepattern("file1.txt")
321 .addFilepattern("file2.txt").addFilepattern("file3.txt").call());
322 RevCommit commit = git.commit().setMessage("add files").call();
323 assertNotNull(commit);
324
325 DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
326 int file1Size = cache.getEntry("file1.txt").getLength();
327 int file2Size = cache.getEntry("file2.txt").getLength();
328 int file3Size = cache.getEntry("file3.txt").getLength();
329 ObjectId file2Id = cache.getEntry("file2.txt").getObjectId();
330 ObjectId file3Id = cache.getEntry("file3.txt").getObjectId();
331 assertTrue(file1Size > 0);
332 assertTrue(file2Size > 0);
333 assertTrue(file3Size > 0);
334
335
336 cache = DirCache.lock(db.getIndexFile(), db.getFS());
337 cache.getEntry("file1.txt").setLength(0);
338 cache.getEntry("file2.txt").setLength(0);
339 cache.getEntry("file3.txt").setLength(0);
340 cache.write();
341 assertTrue(cache.commit());
342
343
344 cache = DirCache.read(db.getIndexFile(), db.getFS());
345 assertEquals(0, cache.getEntry("file1.txt").getLength());
346 assertEquals(0, cache.getEntry("file2.txt").getLength());
347 assertEquals(0, cache.getEntry("file3.txt").getLength());
348
349 long indexTime = db.getIndexFile().lastModified();
350 db.getIndexFile().setLastModified(indexTime - 5000);
351
352 write(file1, "content4");
353 assertTrue(file1.setLastModified(file1.lastModified() + 2500));
354 assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
355 .call());
356
357 cache = db.readDirCache();
358 assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
359 assertEquals(file2Size, cache.getEntry("file2.txt").getLength());
360 assertEquals(file3Size, cache.getEntry("file3.txt").getLength());
361 assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId());
362 assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId());
363 }
364 }
365
366 @Ignore("very flaky when run with Hudson")
367 @Test
368 public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
369 try (Git git = new Git(db)) {
370 File file1 = writeTrashFile("file1.txt", "content1");
371 assertTrue(file1.setLastModified(file1.lastModified() - 5000));
372 File file2 = writeTrashFile("file2.txt", "content2");
373 assertTrue(file2.setLastModified(file2.lastModified() - 5000));
374
375 assertNotNull(git.add().addFilepattern("file1.txt")
376 .addFilepattern("file2.txt").call());
377 RevCommit commit = git.commit().setMessage("add files").call();
378 assertNotNull(commit);
379
380 DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
381 int file1Size = cache.getEntry("file1.txt").getLength();
382 int file2Size = cache.getEntry("file2.txt").getLength();
383 assertTrue(file1Size > 0);
384 assertTrue(file2Size > 0);
385
386 writeTrashFile("file2.txt", "content3");
387 assertNotNull(git.add().addFilepattern("file2.txt").call());
388 writeTrashFile("file2.txt", "content4");
389
390
391 cache = DirCache.lock(db.getIndexFile(), db.getFS());
392 cache.getEntry("file1.txt").setLength(0);
393 cache.getEntry("file2.txt").setLength(0);
394 cache.write();
395 assertTrue(cache.commit());
396
397
398 cache = db.readDirCache();
399 assertEquals(0, cache.getEntry("file1.txt").getLength());
400 assertEquals(0, cache.getEntry("file2.txt").getLength());
401
402 long indexTime = db.getIndexFile().lastModified();
403 db.getIndexFile().setLastModified(indexTime - 5000);
404
405 write(file1, "content5");
406 assertTrue(file1.setLastModified(file1.lastModified() + 1000));
407
408 assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
409 .call());
410
411 cache = db.readDirCache();
412 assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
413 assertEquals(0, cache.getEntry("file2.txt").getLength());
414 }
415 }
416
417 @Test
418 public void commitAfterSquashMerge() throws Exception {
419 try (Git git = new Git(db)) {
420 writeTrashFile("file1", "file1");
421 git.add().addFilepattern("file1").call();
422 RevCommit first = git.commit().setMessage("initial commit").call();
423
424 assertTrue(new File(db.getWorkTree(), "file1").exists());
425 createBranch(first, "refs/heads/branch1");
426 checkoutBranch("refs/heads/branch1");
427
428 writeTrashFile("file2", "file2");
429 git.add().addFilepattern("file2").call();
430 git.commit().setMessage("second commit").call();
431 assertTrue(new File(db.getWorkTree(), "file2").exists());
432
433 checkoutBranch("refs/heads/master");
434
435 MergeResult result = git.merge()
436 .include(db.exactRef("refs/heads/branch1"))
437 .setSquash(true)
438 .call();
439
440 assertTrue(new File(db.getWorkTree(), "file1").exists());
441 assertTrue(new File(db.getWorkTree(), "file2").exists());
442 assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
443 result.getMergeStatus());
444
445
446 RevCommit squashedCommit = git.commit().call();
447
448 assertEquals(1, squashedCommit.getParentCount());
449 assertNull(db.readSquashCommitMsg());
450 assertEquals("commit: Squashed commit of the following:", db
451 .getReflogReader(Constants.HEAD).getLastEntry().getComment());
452 assertEquals("commit: Squashed commit of the following:", db
453 .getReflogReader(db.getBranch()).getLastEntry().getComment());
454 }
455 }
456
457 @Test
458 public void testReflogs() throws Exception {
459 try (Git git = new Git(db)) {
460 writeTrashFile("f", "1");
461 git.add().addFilepattern("f").call();
462 git.commit().setMessage("c1").call();
463 writeTrashFile("f", "2");
464 git.commit().setMessage("c2").setAll(true).setReflogComment(null)
465 .call();
466 writeTrashFile("f", "3");
467 git.commit().setMessage("c3").setAll(true)
468 .setReflogComment("testRl").call();
469
470 db.getReflogReader(Constants.HEAD).getReverseEntries();
471
472 assertEquals("testRl;commit (initial): c1;", reflogComments(
473 db.getReflogReader(Constants.HEAD).getReverseEntries()));
474 assertEquals("testRl;commit (initial): c1;", reflogComments(
475 db.getReflogReader(db.getBranch()).getReverseEntries()));
476 }
477 }
478
479 private static String reflogComments(List<ReflogEntry> entries) {
480 StringBuffer b = new StringBuffer();
481 for (ReflogEntry e : entries) {
482 b.append(e.getComment()).append(";");
483 }
484 return b.toString();
485 }
486
487 @Test(expected = WrongRepositoryStateException.class)
488 public void commitAmendOnInitialShouldFail() throws Exception {
489 try (Git git = new Git(db)) {
490 git.commit().setAmend(true).setMessage("initial commit").call();
491 }
492 }
493
494 @Test
495 public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime()
496 throws Exception {
497 try (Git git = new Git(db)) {
498 writeTrashFile("file1", "file1");
499 git.add().addFilepattern("file1").call();
500
501 final String authorName = "First Author";
502 final String authorEmail = "author@example.org";
503 final Date authorDate = new Date(1349621117000L);
504 PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail,
505 authorDate, TimeZone.getTimeZone("UTC"));
506 git.commit().setMessage("initial commit").setAuthor(firstAuthor).call();
507
508 RevCommit amended = git.commit().setAmend(true)
509 .setMessage("amend commit").call();
510
511 PersonIdent amendedAuthor = amended.getAuthorIdent();
512 assertEquals(authorName, amendedAuthor.getName());
513 assertEquals(authorEmail, amendedAuthor.getEmailAddress());
514 assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime());
515 }
516 }
517
518 @Test
519 public void commitAmendWithAuthorShouldUseIt() throws Exception {
520 try (Git git = new Git(db)) {
521 writeTrashFile("file1", "file1");
522 git.add().addFilepattern("file1").call();
523 git.commit().setMessage("initial commit").call();
524
525 RevCommit amended = git.commit().setAmend(true)
526 .setAuthor("New Author", "newauthor@example.org")
527 .setMessage("amend commit").call();
528
529 PersonIdent amendedAuthor = amended.getAuthorIdent();
530 assertEquals("New Author", amendedAuthor.getName());
531 assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress());
532 }
533 }
534
535 @Test
536 public void commitEmptyCommits() throws Exception {
537 try (Git git = new Git(db)) {
538
539 writeTrashFile("file1", "file1");
540 git.add().addFilepattern("file1").call();
541 RevCommit initial = git.commit().setMessage("initial commit")
542 .call();
543
544 RevCommit emptyFollowUp = git.commit()
545 .setAuthor("New Author", "newauthor@example.org")
546 .setMessage("no change").call();
547
548 assertNotEquals(initial.getId(), emptyFollowUp.getId());
549 assertEquals(initial.getTree().getId(),
550 emptyFollowUp.getTree().getId());
551
552 try {
553 git.commit().setAuthor("New Author", "newauthor@example.org")
554 .setMessage("again no change").setAllowEmpty(false)
555 .call();
556 fail("Didn't get the expected EmtpyCommitException");
557 } catch (EmtpyCommitException e) {
558
559 }
560
561
562 git.commit().setAuthor("New Author", "newauthor@example.org")
563 .setMessage("again no change").setOnly("file1")
564 .setAllowEmpty(true).call();
565 }
566 }
567
568 @Test
569 public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers()
570 throws Exception {
571 DirCache index = db.lockDirCache();
572 DirCacheBuilder builder = index.builder();
573 addUnmergedEntry("unmerged1", builder);
574 addUnmergedEntry("unmerged2", builder);
575 DirCacheEntry other = new DirCacheEntry("other");
576 other.setFileMode(FileMode.REGULAR_FILE);
577 builder.add(other);
578 builder.commit();
579
580 writeTrashFile("unmerged1", "unmerged1 data");
581 writeTrashFile("unmerged2", "unmerged2 data");
582 writeTrashFile("other", "other data");
583
584 assertEquals("[other, mode:100644]"
585 + "[unmerged1, mode:100644, stage:1]"
586 + "[unmerged1, mode:100644, stage:2]"
587 + "[unmerged1, mode:100644, stage:3]"
588 + "[unmerged2, mode:100644, stage:1]"
589 + "[unmerged2, mode:100644, stage:2]"
590 + "[unmerged2, mode:100644, stage:3]",
591 indexState(0));
592
593 try (Git git = new Git(db)) {
594 RevCommit commit = git.commit().setOnly("unmerged1")
595 .setMessage("Only one file").call();
596
597 assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]"
598 + "[unmerged2, mode:100644, stage:1]"
599 + "[unmerged2, mode:100644, stage:2]"
600 + "[unmerged2, mode:100644, stage:3]",
601 indexState(0));
602
603 try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) {
604 assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
605 }
606 }
607 }
608
609 @Test
610 public void commitOnlyShouldHandleIgnored() throws Exception {
611 try (Git git = new Git(db)) {
612 writeTrashFile("subdir/foo", "Hello World");
613 writeTrashFile("subdir/bar", "Hello World");
614 writeTrashFile(".gitignore", "bar");
615 git.add().addFilepattern("subdir").call();
616 git.commit().setOnly("subdir").setMessage("first commit").call();
617 }
618 }
619
620 private static void addUnmergedEntry(String file, DirCacheBuilder builder) {
621 DirCacheEntry stage1 = new DirCacheEntry(file, DirCacheEntry.STAGE_1);
622 DirCacheEntry stage2 = new DirCacheEntry(file, DirCacheEntry.STAGE_2);
623 DirCacheEntry stage3 = new DirCacheEntry(file, DirCacheEntry.STAGE_3);
624 stage1.setFileMode(FileMode.REGULAR_FILE);
625 stage2.setFileMode(FileMode.REGULAR_FILE);
626 stage3.setFileMode(FileMode.REGULAR_FILE);
627 builder.add(stage1);
628 builder.add(stage2);
629 builder.add(stage3);
630 }
631 }