Merge branch 'stable-1.2'

* stable-1.2:
  Add API checking using clirr
  Fix MergeCommandTest to pass if File.executable is not supported
  Fix ResolveMerger not to add paths with FileMode 0

Change-Id: I86e7194a40acd6dfa3d433f1d17c01bdf5bb0d9c
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index fcd6f89..9a55466 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -110,6 +110,25 @@
 					<encoding>UTF-8</encoding>
 				</configuration>
 			</plugin>
+
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>clirr-maven-plugin</artifactId>
+			</plugin>
 		</plugins>
 	</build>
+
+	<reporting>
+		<plugins>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>clirr-maven-plugin</artifactId>
+				<version>${clirr-version}</version>
+				<configuration>
+					<comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+					<minSeverity>info</minSeverity>
+				</configuration>
+			</plugin>
+		</plugins>
+	</reporting>
 </project>
diff --git a/org.eclipse.jgit.console/pom.xml b/org.eclipse.jgit.console/pom.xml
index c5573c5..bd99d39 100644
--- a/org.eclipse.jgit.console/pom.xml
+++ b/org.eclipse.jgit.console/pom.xml
@@ -108,6 +108,25 @@
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+      </plugin>
     </plugins>
   </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>${clirr-version}</version>
+        <configuration>
+          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+          <minSeverity>info</minSeverity>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 94479cc..d4fc39e 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -125,6 +125,25 @@
           </archive>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+      </plugin>
     </plugins>
   </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>${clirr-version}</version>
+        <configuration>
+          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+          <minSeverity>info</minSeverity>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
diff --git a/org.eclipse.jgit.storage.dht/pom.xml b/org.eclipse.jgit.storage.dht/pom.xml
index 83c1b85..06ca61d 100644
--- a/org.eclipse.jgit.storage.dht/pom.xml
+++ b/org.eclipse.jgit.storage.dht/pom.xml
@@ -157,6 +157,25 @@
                </execution>
             </executions>
          </plugin>
+
+         <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>clirr-maven-plugin</artifactId>
+         </plugin>
     </plugins>
   </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>${clirr-version}</version>
+        <configuration>
+          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+          <minSeverity>info</minSeverity>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index bb90588..30a9452 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.merge.MergeStrategy;
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Test;
 import org.junit.experimental.theories.DataPoints;
@@ -1023,6 +1024,88 @@ public void testMergeRemovingFolders() throws Exception {
 		assertFalse(folder2.exists());
 	}
 
+	@Test
+	public void testFileModeMerge() throws Exception {
+		if (!FS.DETECTED.supportsExecute())
+			return;
+		// Only Java6
+		Git git = new Git(db);
+
+		writeTrashFile("mergeableMode", "a");
+		setExecutable(git, "mergeableMode", false);
+		writeTrashFile("conflictingModeWithBase", "a");
+		setExecutable(git, "conflictingModeWithBase", false);
+		RevCommit initialCommit = addAllAndCommit(git);
+
+		// switch branch
+		createBranch(initialCommit, "refs/heads/side");
+		checkoutBranch("refs/heads/side");
+		setExecutable(git, "mergeableMode", true);
+		writeTrashFile("conflictingModeNoBase", "b");
+		setExecutable(git, "conflictingModeNoBase", true);
+		RevCommit sideCommit = addAllAndCommit(git);
+
+		// switch branch
+		createBranch(initialCommit, "refs/heads/side2");
+		checkoutBranch("refs/heads/side2");
+		setExecutable(git, "mergeableMode", false);
+		assertFalse(new File(git.getRepository().getWorkTree(),
+				"conflictingModeNoBase").exists());
+		writeTrashFile("conflictingModeNoBase", "b");
+		setExecutable(git, "conflictingModeNoBase", false);
+		addAllAndCommit(git);
+
+		// merge
+		MergeResult result = git.merge().include(sideCommit.getId())
+				.setStrategy(MergeStrategy.RESOLVE).call();
+		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		assertTrue(canExecute(git, "mergeableMode"));
+		assertFalse(canExecute(git, "conflictingModeNoBase"));
+	}
+
+	@Test
+	public void testFileModeMergeWithDirtyWorkTree() throws Exception {
+		if (!FS.DETECTED.supportsExecute())
+			return;
+		// Only Java6 (or set x bit in index)
+
+		Git git = new Git(db);
+
+		writeTrashFile("mergeableButDirty", "a");
+		setExecutable(git, "mergeableButDirty", false);
+		RevCommit initialCommit = addAllAndCommit(git);
+
+		// switch branch
+		createBranch(initialCommit, "refs/heads/side");
+		checkoutBranch("refs/heads/side");
+		setExecutable(git, "mergeableButDirty", true);
+		RevCommit sideCommit = addAllAndCommit(git);
+
+		// switch branch
+		createBranch(initialCommit, "refs/heads/side2");
+		checkoutBranch("refs/heads/side2");
+		setExecutable(git, "mergeableButDirty", false);
+		addAllAndCommit(git);
+
+		writeTrashFile("mergeableButDirty", "b");
+
+		// merge
+		MergeResult result = git.merge().include(sideCommit.getId())
+				.setStrategy(MergeStrategy.RESOLVE).call();
+		assertEquals(MergeStatus.FAILED, result.getMergeStatus());
+		assertFalse(canExecute(git, "mergeableButDirty"));
+	}
+
+	private void setExecutable(Git git, String path, boolean executable) {
+		FS.DETECTED.setExecute(
+				new File(git.getRepository().getWorkTree(), path), executable);
+	}
+
+	private boolean canExecute(Git git, String path) {
+		return FS.DETECTED.canExecute(new File(git.getRepository()
+				.getWorkTree(), path));
+	}
+
 	private RevCommit addAllAndCommit(final Git git) throws Exception {
 		git.add().addFilepattern(".").call();
 		return git.commit().setMessage("message").call();
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index b77a17a..92f39ac 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -142,7 +142,13 @@
           </archive>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+      </plugin>
     </plugins>
+
     <pluginManagement>
       <plugins>
         <plugin>
@@ -155,4 +161,18 @@
       </plugins>
     </pluginManagement>
   </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>${clirr-version}</version>
+        <configuration>
+          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+          <minSeverity>info</minSeverity>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
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 b763568..8211780 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -51,6 +51,7 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -378,12 +379,46 @@ private boolean processEntry(CanonicalTreeParser base,
 		if (isIndexDirty())
 			return false;
 
-		if (nonTree(modeO) && modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) {
-			// OURS and THEIRS are equal: it doesn't matter which one we choose.
-			// OURS is chosen.
-			add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
-			// no checkout needed!
-			return true;
+		if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
+			// OURS and THEIRS have equal content. Check the file mode
+			if (modeO == modeT) {
+				// content and mode of OURS and THEIRS are equal: it doesn't
+				// matter which one we choose. OURS is chosen.
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
+				// no checkout needed!
+				return true;
+			} else {
+				// same content but different mode on OURS and THEIRS.
+				// Try to merge the mode and report an error if this is
+				// not possible.
+				int newMode = mergeFileModes(modeB, modeO, modeT);
+				if (newMode != FileMode.MISSING.getBits()) {
+					if (newMode == modeO)
+						// ours version is preferred
+						add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
+					else {
+						// the preferred version THEIRS has a different mode
+						// than ours. Check it out!
+						if (isWorktreeDirty())
+							return false;
+						DirCacheEntry e = add(tw.getRawPath(), theirs,
+								DirCacheEntry.STAGE_0);
+						toBeCheckedOut.put(tw.getPathString(), e);
+					}
+					return true;
+				} else {
+					// FileModes are not mergeable. We found a conflict on modes
+					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
+					add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
+					add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
+					unmergedPaths.add(tw.getPathString());
+					mergeResults.put(
+							tw.getPathString(),
+							new MergeResult<RawText>(Collections
+									.<RawText> emptyList()));
+				}
+				return true;
+			}
 		}
 
 		if (nonTree(modeO) && modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
@@ -582,7 +617,12 @@ else if (!result.containsConflicts()) {
 			// no conflict occurred, the file will contain fully merged content.
 			// the index will be populated with the new merged version
 			DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
-			dce.setFileMode(tw.getFileMode(0));
+			int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
+					tw.getRawMode(2));
+			// set the mode for the new content. Fall back to REGULAR_FILE if
+			// you can't merge modes of OURS and THEIRS
+			dce.setFileMode((newMode == FileMode.MISSING.getBits()) ? FileMode.REGULAR_FILE
+					: FileMode.fromBits(newMode));
 			dce.setLastModified(of.lastModified());
 			dce.setLength((int) of.length());
 			InputStream is = new FileInputStream(of);
@@ -599,6 +639,34 @@ else if (!result.containsConflicts()) {
 		}
 	}
 
+	/**
+	 * Try to merge filemodes. If only ours or theirs have changed the mode
+	 * (compared to base) we choose that one. If ours and theirs have equal
+	 * modes return that one. If also that is not the case the modes are not
+	 * mergeable. Return {@link FileMode#MISSING} int that case.
+	 *
+	 * @param modeB
+	 *            filemode found in BASE
+	 * @param modeO
+	 *            filemode found in OURS
+	 * @param modeT
+	 *            filemode found in THEIRS
+	 *
+	 * @return the merged filemode or {@link FileMode#MISSING} in case of a
+	 *         conflict
+	 */
+	private int mergeFileModes(int modeB, int modeO, int modeT) {
+		if (modeO == modeT)
+			return modeO;
+		if (modeB == modeO)
+			// Base equal to Ours -> chooses Theirs if that is not missing
+			return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
+		if (modeB == modeT)
+			// Base equal to Theirs -> chooses Ours if that is not missing
+			return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
+		return FileMode.MISSING.getBits();
+	}
+
 	private static RawText getRawText(ObjectId id, Repository db)
 			throws IOException {
 		if (id.equals(ObjectId.zeroId()))
diff --git a/pom.xml b/pom.xml
index 15365b0..b74db9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,14 +130,23 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
+    <jgit-last-release-version>1.1.0.201109151100-r</jgit-last-release-version>
     <jsch-version>0.1.44-1</jsch-version>
     <junit-version>4.5</junit-version>
     <args4j-version>2.0.12</args4j-version>
     <servlet-api-version>2.5</servlet-api-version>
     <jetty-version>7.1.6.v20100715</jetty-version>
     <protobuf-version>2.4.0a</protobuf-version>
+    <clirr-version>2.3</clirr-version>
   </properties>
 
+  <repositories>
+    <repository>
+      <id>jgit-repository</id>
+      <url>http://download.eclipse.org/jgit/maven</url>
+    </repository>
+  </repositories>
+
   <build>
     <pluginManagement>
       <plugins>
@@ -246,6 +255,16 @@
             </execution>
           </executions>
         </plugin>
+
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>clirr-maven-plugin</artifactId>
+          <version>${clirr-version}</version>
+          <configuration>
+            <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
+            <minSeverity>info</minSeverity>
+          </configuration>
+        </plugin>
       </plugins>
     </pluginManagement>