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:
commit
42b0fb4679
|
@ -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);
|
||||||
|
|
|
@ -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())) {
|
||||||
|
|
Loading…
Reference in New Issue