| /* |
| * Copyright (C) 2011-2012, GitHub Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| package org.eclipse.jgit.api; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.File; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.TimeZone; |
| |
| import org.eclipse.jgit.api.errors.WrongRepositoryStateException; |
| import org.eclipse.jgit.diff.DiffEntry; |
| import org.eclipse.jgit.dircache.DirCache; |
| import org.eclipse.jgit.dircache.DirCacheBuilder; |
| import org.eclipse.jgit.dircache.DirCacheEntry; |
| import org.eclipse.jgit.junit.RepositoryTestCase; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.FileMode; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.RefUpdate.Result; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.lib.StoredConfig; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.submodule.SubmoduleWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.jgit.treewalk.filter.TreeFilter; |
| import org.eclipse.jgit.util.FS; |
| import org.junit.Test; |
| |
| /** |
| * Unit tests of {@link CommitCommand}. |
| */ |
| public class CommitCommandTest extends RepositoryTestCase { |
| |
| @Test |
| public void testExecutableRetention() throws Exception { |
| StoredConfig config = db.getConfig(); |
| config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, |
| ConfigConstants.CONFIG_KEY_FILEMODE, true); |
| config.save(); |
| |
| FS executableFs = new FS() { |
| |
| public boolean supportsExecute() { |
| return true; |
| } |
| |
| public boolean setExecute(File f, boolean canExec) { |
| return true; |
| } |
| |
| public ProcessBuilder runInShell(String cmd, String[] args) { |
| return null; |
| } |
| |
| public boolean retryFailedLockFileCommit() { |
| return false; |
| } |
| |
| public FS newInstance() { |
| return this; |
| } |
| |
| protected File discoverGitPrefix() { |
| return null; |
| } |
| |
| public boolean canExecute(File f) { |
| return true; |
| } |
| |
| @Override |
| public boolean isCaseSensitive() { |
| return true; |
| } |
| }; |
| |
| Git git = Git.open(db.getDirectory(), executableFs); |
| String path = "a.txt"; |
| writeTrashFile(path, "content"); |
| git.add().addFilepattern(path).call(); |
| RevCommit commit1 = git.commit().setMessage("commit").call(); |
| TreeWalk walk = TreeWalk.forPath(db, path, commit1.getTree()); |
| assertNotNull(walk); |
| assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); |
| |
| FS nonExecutableFs = new FS() { |
| |
| public boolean supportsExecute() { |
| return false; |
| } |
| |
| public boolean setExecute(File f, boolean canExec) { |
| return false; |
| } |
| |
| public ProcessBuilder runInShell(String cmd, String[] args) { |
| return null; |
| } |
| |
| public boolean retryFailedLockFileCommit() { |
| return false; |
| } |
| |
| public FS newInstance() { |
| return this; |
| } |
| |
| protected File discoverGitPrefix() { |
| return null; |
| } |
| |
| public boolean canExecute(File f) { |
| return false; |
| } |
| |
| @Override |
| public boolean isCaseSensitive() { |
| return true; |
| } |
| }; |
| |
| config = db.getConfig(); |
| config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, |
| ConfigConstants.CONFIG_KEY_FILEMODE, false); |
| config.save(); |
| |
| Git git2 = Git.open(db.getDirectory(), nonExecutableFs); |
| writeTrashFile(path, "content2"); |
| RevCommit commit2 = git2.commit().setOnly(path).setMessage("commit2") |
| .call(); |
| walk = TreeWalk.forPath(db, path, commit2.getTree()); |
| assertNotNull(walk); |
| assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); |
| } |
| |
| @Test |
| public void commitNewSubmodule() throws Exception { |
| Git git = new Git(db); |
| writeTrashFile("file.txt", "content"); |
| git.add().addFilepattern("file.txt").call(); |
| RevCommit commit = git.commit().setMessage("create file").call(); |
| |
| SubmoduleAddCommand command = new SubmoduleAddCommand(db); |
| String path = "sub"; |
| command.setPath(path); |
| String uri = db.getDirectory().toURI().toString(); |
| command.setURI(uri); |
| Repository repo = command.call(); |
| assertNotNull(repo); |
| addRepoToClose(repo); |
| |
| SubmoduleWalk generator = SubmoduleWalk.forIndex(db); |
| assertTrue(generator.next()); |
| assertEquals(path, generator.getPath()); |
| assertEquals(commit, generator.getObjectId()); |
| assertEquals(uri, generator.getModulesUrl()); |
| assertEquals(path, generator.getModulesPath()); |
| assertEquals(uri, generator.getConfigUrl()); |
| Repository subModRepo = generator.getRepository(); |
| addRepoToClose(subModRepo); |
| assertNotNull(subModRepo); |
| assertEquals(commit, repo.resolve(Constants.HEAD)); |
| |
| RevCommit submoduleCommit = git.commit().setMessage("submodule add") |
| .setOnly(path).call(); |
| assertNotNull(submoduleCommit); |
| TreeWalk walk = new TreeWalk(db); |
| walk.addTree(commit.getTree()); |
| walk.addTree(submoduleCommit.getTree()); |
| walk.setFilter(TreeFilter.ANY_DIFF); |
| List<DiffEntry> diffs = DiffEntry.scan(walk); |
| assertEquals(1, diffs.size()); |
| DiffEntry subDiff = diffs.get(0); |
| assertEquals(FileMode.MISSING, subDiff.getOldMode()); |
| assertEquals(FileMode.GITLINK, subDiff.getNewMode()); |
| assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId()); |
| assertEquals(commit, subDiff.getNewId().toObjectId()); |
| assertEquals(path, subDiff.getNewPath()); |
| } |
| |
| @Test |
| public void commitSubmoduleUpdate() throws Exception { |
| Git git = new Git(db); |
| writeTrashFile("file.txt", "content"); |
| git.add().addFilepattern("file.txt").call(); |
| RevCommit commit = git.commit().setMessage("create file").call(); |
| writeTrashFile("file.txt", "content2"); |
| git.add().addFilepattern("file.txt").call(); |
| RevCommit commit2 = git.commit().setMessage("edit file").call(); |
| |
| SubmoduleAddCommand command = new SubmoduleAddCommand(db); |
| String path = "sub"; |
| command.setPath(path); |
| String uri = db.getDirectory().toURI().toString(); |
| command.setURI(uri); |
| Repository repo = command.call(); |
| assertNotNull(repo); |
| addRepoToClose(repo); |
| |
| SubmoduleWalk generator = SubmoduleWalk.forIndex(db); |
| assertTrue(generator.next()); |
| assertEquals(path, generator.getPath()); |
| assertEquals(commit2, generator.getObjectId()); |
| assertEquals(uri, generator.getModulesUrl()); |
| assertEquals(path, generator.getModulesPath()); |
| assertEquals(uri, generator.getConfigUrl()); |
| Repository subModRepo = generator.getRepository(); |
| addRepoToClose(subModRepo); |
| assertNotNull(subModRepo); |
| assertEquals(commit2, repo.resolve(Constants.HEAD)); |
| |
| RevCommit submoduleAddCommit = git.commit().setMessage("submodule add") |
| .setOnly(path).call(); |
| assertNotNull(submoduleAddCommit); |
| |
| RefUpdate update = repo.updateRef(Constants.HEAD); |
| update.setNewObjectId(commit); |
| assertEquals(Result.FORCED, update.forceUpdate()); |
| |
| RevCommit submoduleEditCommit = git.commit() |
| .setMessage("submodule add").setOnly(path).call(); |
| assertNotNull(submoduleEditCommit); |
| TreeWalk walk = new TreeWalk(db); |
| walk.addTree(submoduleAddCommit.getTree()); |
| walk.addTree(submoduleEditCommit.getTree()); |
| walk.setFilter(TreeFilter.ANY_DIFF); |
| List<DiffEntry> diffs = DiffEntry.scan(walk); |
| assertEquals(1, diffs.size()); |
| DiffEntry subDiff = diffs.get(0); |
| assertEquals(FileMode.GITLINK, subDiff.getOldMode()); |
| assertEquals(FileMode.GITLINK, subDiff.getNewMode()); |
| assertEquals(commit2, subDiff.getOldId().toObjectId()); |
| assertEquals(commit, subDiff.getNewId().toObjectId()); |
| assertEquals(path, subDiff.getNewPath()); |
| assertEquals(path, subDiff.getOldPath()); |
| } |
| |
| @Test |
| public void commitUpdatesSmudgedEntries() throws Exception { |
| Git git = new Git(db); |
| |
| File file1 = writeTrashFile("file1.txt", "content1"); |
| assertTrue(file1.setLastModified(file1.lastModified() - 5000)); |
| File file2 = writeTrashFile("file2.txt", "content2"); |
| assertTrue(file2.setLastModified(file2.lastModified() - 5000)); |
| File file3 = writeTrashFile("file3.txt", "content3"); |
| assertTrue(file3.setLastModified(file3.lastModified() - 5000)); |
| |
| assertNotNull(git.add().addFilepattern("file1.txt") |
| .addFilepattern("file2.txt").addFilepattern("file3.txt").call()); |
| RevCommit commit = git.commit().setMessage("add files").call(); |
| assertNotNull(commit); |
| |
| DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); |
| int file1Size = cache.getEntry("file1.txt").getLength(); |
| int file2Size = cache.getEntry("file2.txt").getLength(); |
| int file3Size = cache.getEntry("file3.txt").getLength(); |
| ObjectId file2Id = cache.getEntry("file2.txt").getObjectId(); |
| ObjectId file3Id = cache.getEntry("file3.txt").getObjectId(); |
| assertTrue(file1Size > 0); |
| assertTrue(file2Size > 0); |
| assertTrue(file3Size > 0); |
| |
| // Smudge entries |
| cache = DirCache.lock(db.getIndexFile(), db.getFS()); |
| cache.getEntry("file1.txt").setLength(0); |
| cache.getEntry("file2.txt").setLength(0); |
| cache.getEntry("file3.txt").setLength(0); |
| cache.write(); |
| assertTrue(cache.commit()); |
| |
| // Verify entries smudged |
| cache = DirCache.read(db.getIndexFile(), db.getFS()); |
| assertEquals(0, cache.getEntry("file1.txt").getLength()); |
| assertEquals(0, cache.getEntry("file2.txt").getLength()); |
| assertEquals(0, cache.getEntry("file3.txt").getLength()); |
| |
| long indexTime = db.getIndexFile().lastModified(); |
| db.getIndexFile().setLastModified(indexTime - 5000); |
| |
| write(file1, "content4"); |
| assertTrue(file1.setLastModified(file1.lastModified() + 2500)); |
| assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") |
| .call()); |
| |
| cache = db.readDirCache(); |
| assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); |
| assertEquals(file2Size, cache.getEntry("file2.txt").getLength()); |
| assertEquals(file3Size, cache.getEntry("file3.txt").getLength()); |
| assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId()); |
| assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId()); |
| } |
| |
| @Test |
| public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { |
| Git git = new Git(db); |
| |
| File file1 = writeTrashFile("file1.txt", "content1"); |
| assertTrue(file1.setLastModified(file1.lastModified() - 5000)); |
| File file2 = writeTrashFile("file2.txt", "content2"); |
| assertTrue(file2.setLastModified(file2.lastModified() - 5000)); |
| |
| assertNotNull(git.add().addFilepattern("file1.txt") |
| .addFilepattern("file2.txt").call()); |
| RevCommit commit = git.commit().setMessage("add files").call(); |
| assertNotNull(commit); |
| |
| DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); |
| int file1Size = cache.getEntry("file1.txt").getLength(); |
| int file2Size = cache.getEntry("file2.txt").getLength(); |
| assertTrue(file1Size > 0); |
| assertTrue(file2Size > 0); |
| |
| writeTrashFile("file2.txt", "content3"); |
| assertNotNull(git.add().addFilepattern("file2.txt").call()); |
| writeTrashFile("file2.txt", "content4"); |
| |
| // Smudge entries |
| cache = DirCache.lock(db.getIndexFile(), db.getFS()); |
| cache.getEntry("file1.txt").setLength(0); |
| cache.getEntry("file2.txt").setLength(0); |
| cache.write(); |
| assertTrue(cache.commit()); |
| |
| // Verify entries smudged |
| cache = db.readDirCache(); |
| assertEquals(0, cache.getEntry("file1.txt").getLength()); |
| assertEquals(0, cache.getEntry("file2.txt").getLength()); |
| |
| long indexTime = db.getIndexFile().lastModified(); |
| db.getIndexFile().setLastModified(indexTime - 5000); |
| |
| write(file1, "content5"); |
| assertTrue(file1.setLastModified(file1.lastModified() + 1000)); |
| |
| assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") |
| .call()); |
| |
| cache = db.readDirCache(); |
| assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); |
| assertEquals(0, cache.getEntry("file2.txt").getLength()); |
| } |
| |
| @Test |
| public void commitAfterSquashMerge() throws Exception { |
| Git git = new Git(db); |
| |
| writeTrashFile("file1", "file1"); |
| git.add().addFilepattern("file1").call(); |
| RevCommit first = git.commit().setMessage("initial commit").call(); |
| |
| assertTrue(new File(db.getWorkTree(), "file1").exists()); |
| createBranch(first, "refs/heads/branch1"); |
| checkoutBranch("refs/heads/branch1"); |
| |
| writeTrashFile("file2", "file2"); |
| git.add().addFilepattern("file2").call(); |
| git.commit().setMessage("second commit").call(); |
| assertTrue(new File(db.getWorkTree(), "file2").exists()); |
| |
| checkoutBranch("refs/heads/master"); |
| |
| MergeResult result = git.merge().include(db.getRef("branch1")) |
| .setSquash(true).call(); |
| |
| assertTrue(new File(db.getWorkTree(), "file1").exists()); |
| assertTrue(new File(db.getWorkTree(), "file2").exists()); |
| assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, |
| result.getMergeStatus()); |
| |
| // comment not set, should be inferred from SQUASH_MSG |
| RevCommit squashedCommit = git.commit().call(); |
| |
| assertEquals(1, squashedCommit.getParentCount()); |
| assertNull(db.readSquashCommitMsg()); |
| assertEquals("commit: Squashed commit of the following:", db |
| .getReflogReader(Constants.HEAD).getLastEntry().getComment()); |
| assertEquals("commit: Squashed commit of the following:", db |
| .getReflogReader(db.getBranch()).getLastEntry().getComment()); |
| } |
| |
| @Test(expected = WrongRepositoryStateException.class) |
| public void commitAmendOnInitialShouldFail() throws Exception { |
| Git git = new Git(db); |
| git.commit().setAmend(true).setMessage("initial commit").call(); |
| } |
| |
| @Test |
| public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime() |
| throws Exception { |
| Git git = new Git(db); |
| |
| writeTrashFile("file1", "file1"); |
| git.add().addFilepattern("file1").call(); |
| |
| final String authorName = "First Author"; |
| final String authorEmail = "author@example.org"; |
| final Date authorDate = new Date(1349621117000L); |
| PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail, |
| authorDate, TimeZone.getTimeZone("UTC")); |
| git.commit().setMessage("initial commit").setAuthor(firstAuthor).call(); |
| |
| RevCommit amended = git.commit().setAmend(true) |
| .setMessage("amend commit").call(); |
| |
| PersonIdent amendedAuthor = amended.getAuthorIdent(); |
| assertEquals(authorName, amendedAuthor.getName()); |
| assertEquals(authorEmail, amendedAuthor.getEmailAddress()); |
| assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime()); |
| } |
| |
| @Test |
| public void commitAmendWithAuthorShouldUseIt() throws Exception { |
| Git git = new Git(db); |
| |
| writeTrashFile("file1", "file1"); |
| git.add().addFilepattern("file1").call(); |
| git.commit().setMessage("initial commit").call(); |
| |
| RevCommit amended = git.commit().setAmend(true) |
| .setAuthor("New Author", "newauthor@example.org") |
| .setMessage("amend commit").call(); |
| |
| PersonIdent amendedAuthor = amended.getAuthorIdent(); |
| assertEquals("New Author", amendedAuthor.getName()); |
| assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress()); |
| } |
| |
| @Test |
| public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers() |
| throws Exception { |
| DirCache index = db.lockDirCache(); |
| DirCacheBuilder builder = index.builder(); |
| addUnmergedEntry("unmerged1", builder); |
| addUnmergedEntry("unmerged2", builder); |
| DirCacheEntry other = new DirCacheEntry("other"); |
| other.setFileMode(FileMode.REGULAR_FILE); |
| builder.add(other); |
| builder.commit(); |
| |
| writeTrashFile("unmerged1", "unmerged1 data"); |
| writeTrashFile("unmerged2", "unmerged2 data"); |
| writeTrashFile("other", "other data"); |
| |
| assertEquals("[other, mode:100644]" |
| + "[unmerged1, mode:100644, stage:1]" |
| + "[unmerged1, mode:100644, stage:2]" |
| + "[unmerged1, mode:100644, stage:3]" |
| + "[unmerged2, mode:100644, stage:1]" |
| + "[unmerged2, mode:100644, stage:2]" |
| + "[unmerged2, mode:100644, stage:3]", |
| indexState(0)); |
| |
| Git git = new Git(db); |
| RevCommit commit = git.commit().setOnly("unmerged1") |
| .setMessage("Only one file").call(); |
| |
| assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]" |
| + "[unmerged2, mode:100644, stage:1]" |
| + "[unmerged2, mode:100644, stage:2]" |
| + "[unmerged2, mode:100644, stage:3]", |
| indexState(0)); |
| |
| TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree()); |
| assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0)); |
| walk.release(); |
| } |
| |
| private static void addUnmergedEntry(String file, DirCacheBuilder builder) { |
| DirCacheEntry stage1 = new DirCacheEntry(file, DirCacheEntry.STAGE_1); |
| DirCacheEntry stage2 = new DirCacheEntry(file, DirCacheEntry.STAGE_2); |
| DirCacheEntry stage3 = new DirCacheEntry(file, DirCacheEntry.STAGE_3); |
| stage1.setFileMode(FileMode.REGULAR_FILE); |
| stage2.setFileMode(FileMode.REGULAR_FILE); |
| stage3.setFileMode(FileMode.REGULAR_FILE); |
| builder.add(stage1); |
| builder.add(stage2); |
| builder.add(stage3); |
| } |
| } |