You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
528 lines
18 KiB
528 lines
18 KiB
/* |
|
* 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); |
|
} |
|
}
|
|
|