Use working tree iterator to compare file modes

Add isModeDifferent method to WorkingTreeIterator
that compares mode with consideration of the
core.filemode setting in the config.

Bug: 379004
Change-Id: I07335300d787a69c3d1608242238991d5b5214ac
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index 9f92c04..469739c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -44,6 +44,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -54,7 +55,10 @@
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@@ -183,6 +187,42 @@ public void testCherryPickConflictReset() throws Exception {
 				.exists());
 	}
 
+	@Test
+	public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
+			throws Exception {
+		Git git = new Git(db);
+		File file = writeTrashFile("test.txt", "a");
+		assertNotNull(git.add().addFilepattern("test.txt").call());
+		assertNotNull(git.commit().setMessage("commit1").call());
+
+		assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
+
+		writeTrashFile("test.txt", "b");
+		assertNotNull(git.add().addFilepattern("test.txt").call());
+		RevCommit commit2 = git.commit().setMessage("commit2").call();
+		assertNotNull(commit2);
+
+		assertNotNull(git.checkout().setName(Constants.MASTER).call());
+
+		DirCache cache = db.lockDirCache();
+		cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
+		cache.write();
+		assertTrue(cache.commit());
+		cache.unlock();
+
+		assertNotNull(git.commit().setMessage("commit3").call());
+
+		db.getFS().setExecute(file, false);
+		git.getRepository()
+				.getConfig()
+				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+						ConfigConstants.CONFIG_KEY_FILEMODE, false);
+
+		CherryPickResult result = git.cherryPick().include(commit2).call();
+		assertNotNull(result);
+		assertEquals(CherryPickStatus.OK, result.getStatus());
+	}
+
 	private RevCommit prepareCherryPick(final Git git) throws Exception {
 		// create, add and commit file a
 		writeTrashFile("a", "a");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 261aa9f..2c1aa6f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -399,7 +399,7 @@ private boolean processEntry(CanonicalTreeParser base,
 					else {
 						// the preferred version THEIRS has a different mode
 						// than ours. Check it out!
-						if (isWorktreeDirty())
+						if (isWorktreeDirty(work))
 							return false;
 						DirCacheEntry e = add(tw.getRawPath(), theirs,
 								DirCacheEntry.STAGE_0);
@@ -434,7 +434,7 @@ private boolean processEntry(CanonicalTreeParser base,
 			// THEIRS. THEIRS is chosen.
 
 			// Check worktree before checking out THEIRS
-			if (isWorktreeDirty())
+			if (isWorktreeDirty(work))
 				return false;
 			if (nonTree(modeT)) {
 				DirCacheEntry e = add(tw.getRawPath(), theirs,
@@ -485,7 +485,7 @@ private boolean processEntry(CanonicalTreeParser base,
 
 		if (nonTree(modeO) && nonTree(modeT)) {
 			// Check worktree before modifying files
-			if (isWorktreeDirty())
+			if (isWorktreeDirty(work))
 				return false;
 
 			MergeResult<RawText> result = contentMerge(base, ours, theirs);
@@ -507,7 +507,7 @@ private boolean processEntry(CanonicalTreeParser base,
 				// OURS was deleted checkout THEIRS
 				if (modeO == 0) {
 					// Check worktree before checking out THEIRS
-					if (isWorktreeDirty())
+					if (isWorktreeDirty(work))
 						return false;
 					if (nonTree(modeT)) {
 						if (e != null)
@@ -563,7 +563,7 @@ private boolean isIndexDirty() {
 		return isDirty;
 	}
 
-	private boolean isWorktreeDirty() {
+	private boolean isWorktreeDirty(WorkingTreeIterator work) {
 		if (inCore)
 			return false;
 
@@ -571,8 +571,13 @@ private boolean isWorktreeDirty() {
 		final int modeO = tw.getRawMode(T_OURS);
 
 		// Worktree entry has to match ours to be considered clean
-		final boolean isDirty = nonTree(modeF)
-				&& !(modeO == modeF && tw.idEqual(T_FILE, T_OURS));
+		final boolean isDirty;
+		if (nonTree(modeF))
+			isDirty = work.isModeDifferent(modeO)
+					|| !tw.idEqual(T_FILE, T_OURS);
+		else
+			isDirty = false;
+
 		if (isDirty)
 			failingPaths.put(tw.getPathString(),
 					MergeFailureReason.DIRTY_WORKTREE);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 759613b..ebe9f73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -696,6 +696,33 @@ public enum MetadataDiff {
 	}
 
 	/**
+	 * Is the file mode of the current entry different than the given raw mode?
+	 *
+	 * @param rawMode
+	 * @return true if different, false otherwise
+	 */
+	public boolean isModeDifferent(final int rawMode) {
+		// Determine difference in mode-bits of file and index-entry. In the
+		// bitwise presentation of modeDiff we'll have a '1' when the two modes
+		// differ at this position.
+		int modeDiff = getEntryRawMode() ^ rawMode;
+
+		if (modeDiff == 0)
+			return false;
+
+		// Do not rely on filemode differences in case of symbolic links
+		if (FileMode.SYMLINK.equals(rawMode))
+			return false;
+
+		// Ignore the executable file bits if WorkingTreeOptions tell me to
+		// do so. Ignoring is done by setting the bits representing a
+		// EXECUTABLE_FILE to '0' in modeDiff
+		if (!state.options.isFileMode())
+			modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
+		return modeDiff != 0;
+	}
+
+	/**
 	 * Compare the metadata (mode, length, modification-timestamp) of the
 	 * current entry and a {@link DirCacheEntry}
 	 *
@@ -714,23 +741,8 @@ public MetadataDiff compareMetadata(DirCacheEntry entry) {
 		if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
 			return MetadataDiff.DIFFER_BY_METADATA;
 
-		// Determine difference in mode-bits of file and index-entry. In the
-		// bitwise presentation of modeDiff we'll have a '1' when the two modes
-		// differ at this position.
-		int modeDiff = getEntryRawMode() ^ entry.getRawMode();
-
-		// Do not rely on filemode differences in case of symbolic links
-		if (modeDiff != 0 && !FileMode.SYMLINK.equals(entry.getRawMode())) {
-			// Ignore the executable file bits if WorkingTreeOptions tell me to
-			// do so. Ignoring is done by setting the bits representing a
-			// EXECUTABLE_FILE to '0' in modeDiff
-			if (!state.options.isFileMode())
-				modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
-			if (modeDiff != 0)
-				// Report a modification if the modes still (after potentially
-				// ignoring EXECUTABLE_FILE bits) differ
-				return MetadataDiff.DIFFER_BY_METADATA;
-		}
+		if (isModeDifferent(entry.getRawMode()))
+			return MetadataDiff.DIFFER_BY_METADATA;
 
 		// Git under windows only stores seconds so we round the timestamp
 		// Java gives us if it looks like the timestamp in index is seconds