Merge branch 'stable-1.3'

* stable-1.3:
  Prepare post 1.3.0.201202151440-r build
  JGit 1.3.0.201202151440-r
  Generate conflicts and index updates on file mode changes

Change-Id: Ie99780ef5cdea7b3ea1ea076282fe0a25f14f469
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Matthias Sohn 2012-02-16 00:44:36 +01:00
commit 42b0fb4679
2 changed files with 235 additions and 9 deletions

View File

@ -55,22 +55,27 @@
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
public class DirCacheCheckoutTest extends RepositoryTestCase {
@ -939,6 +944,202 @@ public void testDontOverwriteDirtyFile() throws IOException {
}
}
@Test
public void testFileModeChangeWithNoContentChangeUpdate() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
Git git = Git.wrap(db);
// Add non-executable file
File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit1").call();
assertFalse(db.getFS().canExecute(file));
// Create branch
git.branchCreate().setName("b1").call();
// Make file executable
db.getFS().setExecute(file, true);
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit2").call();
// Verify executable and working directory is clean
Status status = git.status().call();
assertTrue(status.getModified().isEmpty());
assertTrue(status.getChanged().isEmpty());
assertTrue(db.getFS().canExecute(file));
// Switch branches
git.checkout().setName("b1").call();
// Verify not executable and working directory is clean
status = git.status().call();
assertTrue(status.getModified().isEmpty());
assertTrue(status.getChanged().isEmpty());
assertFalse(db.getFS().canExecute(file));
}
@Test
public void testFileModeChangeAndContentChangeConflict() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
Git git = Git.wrap(db);
// Add non-executable file
File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit1").call();
assertFalse(db.getFS().canExecute(file));
// Create branch
git.branchCreate().setName("b1").call();
// Make file executable
db.getFS().setExecute(file, true);
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit2").call();
// Verify executable and working directory is clean
Status status = git.status().call();
assertTrue(status.getModified().isEmpty());
assertTrue(status.getChanged().isEmpty());
assertTrue(db.getFS().canExecute(file));
writeTrashFile("file.txt", "b");
// Switch branches
CheckoutCommand checkout = git.checkout().setName("b1");
try {
checkout.call();
fail("Checkout exception not thrown");
} catch (JGitInternalException e) {
CheckoutResult result = checkout.getResult();
assertNotNull(result);
assertNotNull(result.getConflictList());
assertEquals(1, result.getConflictList().size());
assertTrue(result.getConflictList().contains("file.txt"));
}
}
@Test
public void testDirtyFileModeEqualHeadMerge()
throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
Git git = Git.wrap(db);
// Add non-executable file
File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit1").call();
assertFalse(db.getFS().canExecute(file));
// Create branch
git.branchCreate().setName("b1").call();
// Create second commit and don't touch file
writeTrashFile("file2.txt", "");
git.add().addFilepattern("file2.txt").call();
git.commit().setMessage("commit2").call();
// stage a mode change
writeTrashFile("file.txt", "a");
db.getFS().setExecute(file, true);
git.add().addFilepattern("file.txt").call();
// dirty the file
writeTrashFile("file.txt", "b");
assertEquals(
"[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
// Switch branches and check that the dirty file survived in worktree
// and index
git.checkout().setName("b1").call();
assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "b"));
}
@Test
public void testDirtyFileModeEqualIndexMerge()
throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
Git git = Git.wrap(db);
// Add non-executable file
File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit1").call();
assertFalse(db.getFS().canExecute(file));
// Create branch
git.branchCreate().setName("b1").call();
// Create second commit with executable file
file = writeTrashFile("file.txt", "b");
db.getFS().setExecute(file, true);
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit2").call();
// stage the same content as in the branch we want to switch to
writeTrashFile("file.txt", "a");
db.getFS().setExecute(file, false);
git.add().addFilepattern("file.txt").call();
// dirty the file
writeTrashFile("file.txt", "c");
db.getFS().setExecute(file, true);
assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "c"));
// Switch branches and check that the dirty file survived in worktree
// and index
git.checkout().setName("b1").call();
assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "c"));
}
@Test
public void testFileModeChangeAndContentChangeNoConflict() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
Git git = Git.wrap(db);
// Add first file
File file1 = writeTrashFile("file1.txt", "a");
git.add().addFilepattern("file1.txt").call();
git.commit().setMessage("commit1").call();
assertFalse(db.getFS().canExecute(file1));
// Add second file
File file2 = writeTrashFile("file2.txt", "b");
git.add().addFilepattern("file2.txt").call();
git.commit().setMessage("commit2").call();
assertFalse(db.getFS().canExecute(file2));
// Create branch from first commit
assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
.setStartPoint(Constants.HEAD + "~1").call());
// Change content and file mode in working directory and index
file1 = writeTrashFile("file1.txt", "c");
db.getFS().setExecute(file1, true);
git.add().addFilepattern("file1.txt").call();
// Switch back to 'master'
assertNotNull(git.checkout().setName(Constants.MASTER).call());
}
public void assertWorkDir(HashMap<String, String> i) throws CorruptObjectException,
IOException {
TreeWalk walk = new TreeWalk(db);

View File

@ -472,6 +472,23 @@ private void removeEmptyParents(File f) {
}
}
/**
* Compares whether two pairs of ObjectId and FileMode are equal.
*
* @param id1
* @param mode1
* @param id2
* @param mode2
* @return <code>true</code> if FileModes and ObjectIds are equal.
* <code>false</code> otherwise
*/
private boolean equalIdAndMode(ObjectId id1, FileMode mode1, ObjectId id2,
FileMode mode2) {
if (!mode1.equals(mode2))
return false;
return id1 != null ? id1.equals(id2) : id2 == null;
}
/**
* Here the main work is done. This method is called for each existing path
* in head, index and merge. This method decides what to do with the
@ -508,6 +525,9 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
ObjectId iId = (i == null ? null : i.getEntryObjectId());
ObjectId mId = (m == null ? null : m.getEntryObjectId());
ObjectId hId = (h == null ? null : h.getEntryObjectId());
FileMode iMode = (i == null ? null : i.getEntryFileMode());
FileMode mMode = (m == null ? null : m.getEntryFileMode());
FileMode hMode = (h == null ? null : h.getEntryFileMode());
/**
* <pre>
@ -596,7 +616,7 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
case 0xFDD: // 10 11
// TODO: make use of tree extension as soon as available in jgit
// we would like to do something like
// if (!iId.equals(mId))
// if (!equalIdAndMode(iId, iMode, mId, mMode)
// conflict(name, i.getDirCacheEntry(), h, m);
// But since we don't know the id of a tree in the index we do
// nothing here and wait that conflicts between index and merge
@ -610,7 +630,7 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
conflict(name, (i != null) ? i.getDirCacheEntry() : null, h, m);
break;
case 0xFDF: // 7 8 9
if (hId.equals(mId)) {
if (equalIdAndMode(hId, hMode, mId, mMode)) {
if (isModified(name))
conflict(name, i.getDirCacheEntry(), h, m); // 8
else
@ -625,7 +645,7 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
keep(i.getDirCacheEntry());
break;
case 0xFFD: // 12 13 14
if (hId.equals(iId)) {
if (equalIdAndMode(hId, hMode, iId, iMode)) {
dce = i.getDirCacheEntry();
if (f == null || f.isModified(dce, true))
conflict(name, dce, h, m);
@ -662,7 +682,9 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
if (!FileMode.GITLINK.equals(m.getEntryFileMode())) {
// a dirty worktree: the index is empty but we have a
// workingtree-file
if (mId == null || !mId.equals(f.getEntryObjectId())) {
if (mId == null
|| !equalIdAndMode(mId, mMode,
f.getEntryObjectId(), f.getEntryFileMode())) {
conflict(name, null, h, m);
return;
}
@ -703,7 +725,7 @@ else if (m == null)
* </pre>
*/
if (m == null || mId.equals(iId)) {
if (m == null || equalIdAndMode(mId, mMode, iId, iMode)) {
if (m==null && walk.isDirectoryFileConflict()) {
if (dce != null
&& (f == null || f.isModified(dce, true)))
@ -732,7 +754,7 @@ else if (m == null)
// be removed from the index, but not deleted from disk.
remove(name);
} else {
if (hId.equals(iId)) {
if (equalIdAndMode(hId, hMode, iId, iMode)) {
if (f == null || f.isModified(dce, true))
conflict(name, dce, h, m);
else
@ -741,9 +763,12 @@ else if (m == null)
conflict(name, dce, h, m);
}
} else {
if (!hId.equals(mId) && !hId.equals(iId) && !mId.equals(iId))
if (!equalIdAndMode(hId, hMode, mId, mMode)
&& !equalIdAndMode(hId, hMode, iId, iMode)
&& !equalIdAndMode(mId, mMode, iId, iMode))
conflict(name, dce, h, m);
else if (hId.equals(iId) && !mId.equals(iId)) {
else if (equalIdAndMode(hId, hMode, iId, iMode)
&& !equalIdAndMode(mId, mMode, iId, iMode)) {
// For submodules just update the index with the new SHA-1
if (dce != null
&& FileMode.GITLINK.equals(dce.getFileMode())) {