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.List;
import java.util.Map; 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.Git;
import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.ResetCommand.ResetType; 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.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
import org.junit.Test; import org.junit.Test;
public class DirCacheCheckoutTest extends RepositoryTestCase { 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, public void assertWorkDir(HashMap<String, String> i) throws CorruptObjectException,
IOException { IOException {
TreeWalk walk = new TreeWalk(db); 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 * 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 * 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 iId = (i == null ? null : i.getEntryObjectId());
ObjectId mId = (m == null ? null : m.getEntryObjectId()); ObjectId mId = (m == null ? null : m.getEntryObjectId());
ObjectId hId = (h == null ? null : h.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> * <pre>
@ -596,7 +616,7 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
case 0xFDD: // 10 11 case 0xFDD: // 10 11
// TODO: make use of tree extension as soon as available in jgit // TODO: make use of tree extension as soon as available in jgit
// we would like to do something like // we would like to do something like
// if (!iId.equals(mId)) // if (!equalIdAndMode(iId, iMode, mId, mMode)
// conflict(name, i.getDirCacheEntry(), h, m); // conflict(name, i.getDirCacheEntry(), h, m);
// But since we don't know the id of a tree in the index we do // 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 // 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); conflict(name, (i != null) ? i.getDirCacheEntry() : null, h, m);
break; break;
case 0xFDF: // 7 8 9 case 0xFDF: // 7 8 9
if (hId.equals(mId)) { if (equalIdAndMode(hId, hMode, mId, mMode)) {
if (isModified(name)) if (isModified(name))
conflict(name, i.getDirCacheEntry(), h, m); // 8 conflict(name, i.getDirCacheEntry(), h, m); // 8
else else
@ -625,7 +645,7 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
keep(i.getDirCacheEntry()); keep(i.getDirCacheEntry());
break; break;
case 0xFFD: // 12 13 14 case 0xFFD: // 12 13 14
if (hId.equals(iId)) { if (equalIdAndMode(hId, hMode, iId, iMode)) {
dce = i.getDirCacheEntry(); dce = i.getDirCacheEntry();
if (f == null || f.isModified(dce, true)) if (f == null || f.isModified(dce, true))
conflict(name, dce, h, m); conflict(name, dce, h, m);
@ -662,7 +682,9 @@ void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
if (!FileMode.GITLINK.equals(m.getEntryFileMode())) { if (!FileMode.GITLINK.equals(m.getEntryFileMode())) {
// a dirty worktree: the index is empty but we have a // a dirty worktree: the index is empty but we have a
// workingtree-file // workingtree-file
if (mId == null || !mId.equals(f.getEntryObjectId())) { if (mId == null
|| !equalIdAndMode(mId, mMode,
f.getEntryObjectId(), f.getEntryFileMode())) {
conflict(name, null, h, m); conflict(name, null, h, m);
return; return;
} }
@ -703,7 +725,7 @@ else if (m == null)
* </pre> * </pre>
*/ */
if (m == null || mId.equals(iId)) { if (m == null || equalIdAndMode(mId, mMode, iId, iMode)) {
if (m==null && walk.isDirectoryFileConflict()) { if (m==null && walk.isDirectoryFileConflict()) {
if (dce != null if (dce != null
&& (f == null || f.isModified(dce, true))) && (f == null || f.isModified(dce, true)))
@ -732,7 +754,7 @@ else if (m == null)
// be removed from the index, but not deleted from disk. // be removed from the index, but not deleted from disk.
remove(name); remove(name);
} else { } else {
if (hId.equals(iId)) { if (equalIdAndMode(hId, hMode, iId, iMode)) {
if (f == null || f.isModified(dce, true)) if (f == null || f.isModified(dce, true))
conflict(name, dce, h, m); conflict(name, dce, h, m);
else else
@ -741,9 +763,12 @@ else if (m == null)
conflict(name, dce, h, m); conflict(name, dce, h, m);
} }
} else { } 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); 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 // For submodules just update the index with the new SHA-1
if (dce != null if (dce != null
&& FileMode.GITLINK.equals(dce.getFileMode())) { && FileMode.GITLINK.equals(dce.getFileMode())) {