Merge branch 'stable-4.8' into stable-4.9

* stable-4.8:
  Silence boxing warning
  Prepare 4.5.5-SNAPSHOT builds
  JGit v4.5.4.201711221230-r
  Fix LockFile semantics when running on NFS
  Honor trustFolderStats also when reading packed-refs
  Prepare 4.5.4-SNAPSHOT builds
  JGit v4.5.3.201708160445-r

Change-Id: I7cf2e48934195430b3945b6d74b092f93a3ccd36
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
deleted file mode 100644
index a70ce77..0000000
--- a/org.eclipse.jgit.junit/.settings/.api_filters
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.junit" version="2">
-    <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
-        <filter comment="OK to use internal implementation in tests" id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createBareRepository()"/>
-            </message_arguments>
-        </filter>
-        <filter comment="OK to use internal implementation in tests" id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createRepository(boolean, boolean)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="OK to use internal implementation in tests" id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createWorkRepository()"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/junit/RepositoryTestCase.java" type="org.eclipse.jgit.junit.RepositoryTestCase">
-        <filter comment="OK to use internal implementation in tests" id="627060751">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="RepositoryTestCase"/>
-                <message_argument value="db"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 20d08c3..894341e 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,32 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="CONFIG_KEY_IN_CORE_LIMIT"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="CONFIG_MERGE_SECTION"/>
+            </message_arguments>
+        </filter>
+        <filter id="1141899266">
+            <message_arguments>
+                <message_argument value="4.5"/>
+                <message_argument value="4.9"/>
+                <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/lib/ReflogEntry.java" type="org.eclipse.jgit.lib.ReflogEntry">
         <filter comment="adding enum constant does not break binary compatibility" id="403767336">
             <message_arguments>
@@ -20,34 +47,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/merge/MergeStrategy.java" type="org.eclipse.jgit.merge.MergeStrategy">
-        <filter comment="OSGi semantic versioning allows breaking implementors of an API in a minor version" id="336695337">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.MergeStrategy"/>
-                <message_argument value="newMerger(ObjectInserter, Config)"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
-        <filter comment="OSGi semantic versioning allows breaking implementors of an API in a minor version" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
-                <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/>
-            </message_arguments>
-        </filter>
-        <filter id="1141899266">
-            <message_arguments>
-                <message_argument value="3.5"/>
-                <message_argument value="4.9"/>
-                <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/>
-            </message_arguments>
-        </filter>
-        <filter id="1143996420">
-            <message_arguments>
-                <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection">
         <filter id="403767336">
             <message_arguments>
@@ -68,4 +67,20 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
+        <filter id="1141899266">
+            <message_arguments>
+                <message_argument value="4.5"/>
+                <message_argument value="4.9"/>
+                <message_argument value="createNewFile(File)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1141899266">
+            <message_arguments>
+                <message_argument value="4.5"/>
+                <message_argument value="4.9"/>
+                <message_argument value="supportsAtomicCreateNewFile()"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 6221cfa..9b1fe8c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -168,7 +168,7 @@ public LockFile(final File f) {
 	 */
 	public boolean lock() throws IOException {
 		FileUtils.mkdirs(lck.getParentFile(), true);
-		if (lck.createNewFile()) {
+		if (FS.DETECTED.createNewFile(lck)) {
 			haveLck = true;
 			try {
 				os = new FileOutputStream(lck);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index bb1dc91..a44acac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -86,6 +86,7 @@
 import org.eclipse.jgit.errors.ObjectWritingException;
 import org.eclipse.jgit.events.RefsChangedEvent;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -901,14 +902,20 @@ else if (0 <= (idx = packed.find(dst.getName())))
 	}
 
 	private PackedRefList getPackedRefs() throws IOException {
+		boolean trustFolderStat = getRepository().getConfig().getBoolean(
+				ConfigConstants.CONFIG_CORE_SECTION,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+
 		final PackedRefList curList = packedRefs.get();
-		if (!curList.snapshot.isModified(packedRefsFile))
+		if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
 			return curList;
+		}
 
 		final PackedRefList newList = readPackedRefs();
 		if (packedRefs.compareAndSet(curList, newList)
-				&& !curList.id.equals(newList.id))
+				&& !curList.id.equals(newList.id)) {
 			modCnt.incrementAndGet();
+		}
 		return newList;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index ad5b106..08c883a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -347,6 +347,13 @@ public class ConfigConstants {
 	public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
 
 	/**
+	 * The "supportsAtomicFileCreation" key in the "core section"
+	 *
+	 * @since 4.5
+	 */
+	public static final String CONFIG_KEY_SUPPORTSATOMICFILECREATION = "supportsatomicfilecreation";
+
+	/**
 	 * The "noprefix" key in the "diff section"
 	 * @since 3.0
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 1cc39bd..3e0b41a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -236,6 +236,21 @@ protected FS(FS src) {
 	public abstract boolean supportsExecute();
 
 	/**
+	 * Does this file system support atomic file creation via
+	 * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
+	 * not guaranteed that when two file system clients run createNewFile() in
+	 * parallel only one will succeed. In such cases both clients may think they
+	 * created a new file.
+	 *
+	 * @return true if this implementation support atomic creation of new
+	 *         Files by {@link File#createNewFile()}
+	 * @since 4.5
+	 */
+	public boolean supportsAtomicCreateNewFile() {
+		return true;
+	}
+
+	/**
 	 * Does this operating system and JRE supports symbolic links. The
 	 * capability to handle symbolic links is detected at runtime.
 	 *
@@ -783,6 +798,22 @@ public void createSymLink(File path, String target) throws IOException {
 	}
 
 	/**
+	 * Create a new file. See {@link File#createNewFile()}. Subclasses of this
+	 * class may take care to provide a safe implementation for this even if
+	 * {@link #supportsAtomicCreateNewFile()} is <code>false</code>
+	 *
+	 * @param path
+	 *            the file to be created
+	 * @return <code>true</code> if the file was created, <code>false</code> if
+	 *         the file already existed
+	 * @throws IOException
+	 * @since 4.5
+	 */
+	public boolean createNewFile(File path) throws IOException {
+		return path.createNewFile();
+	}
+
+	/**
 	 * See {@link FileUtils#relativizePath(String, String, String, boolean)}.
 	 *
 	 * @param base
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index 2d58a02..da3b057 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -50,6 +50,7 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -58,8 +59,11 @@
 
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,6 +78,10 @@ public class FS_POSIX extends FS {
 	private static final int DEFAULT_UMASK = 0022;
 	private volatile int umask = -1;
 
+	private volatile boolean supportsUnixNLink = true;
+
+	private volatile Boolean supportsAtomicCreateNewFile;
+
 	/** Default constructor. */
 	protected FS_POSIX() {
 	}
@@ -91,6 +99,34 @@ protected FS_POSIX(FS src) {
 		}
 	}
 
+	@SuppressWarnings("boxing")
+	private void determineAtomicFileCreationSupport() {
+		// @TODO: enhance SystemReader to support this without copying code
+		Boolean ret = getAtomicFileCreationSupportOption(
+				SystemReader.getInstance().openUserConfig(null, this));
+		if (ret == null && StringUtils.isEmptyOrNull(SystemReader.getInstance()
+				.getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
+			ret = getAtomicFileCreationSupportOption(
+					SystemReader.getInstance().openSystemConfig(null, this));
+		}
+		supportsAtomicCreateNewFile = (ret == null) || ret;
+	}
+
+	private Boolean getAtomicFileCreationSupportOption(FileBasedConfig config) {
+		try {
+			config.load();
+			String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
+					null,
+					ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION);
+			if (value == null) {
+				return null;
+			}
+			return Boolean.valueOf(StringUtils.toBoolean(value));
+		} catch (IOException | ConfigInvalidException e) {
+			return Boolean.TRUE;
+		}
+	}
+
 	@Override
 	public FS newInstance() {
 		return new FS_POSIX(this);
@@ -301,4 +337,56 @@ public File findHook(Repository repository, String hookName) {
 			return hookPath.toFile();
 		return null;
 	}
+
+	@Override
+	public boolean supportsAtomicCreateNewFile() {
+		if (supportsAtomicCreateNewFile == null) {
+			determineAtomicFileCreationSupport();
+		}
+		return supportsAtomicCreateNewFile.booleanValue();
+	}
+
+	@Override
+	@SuppressWarnings("boxing")
+	/**
+	 * An implementation of the File#createNewFile() semantics which works also
+	 * on NFS. If the config option
+	 * {@code core.supportsAtomicCreateNewFile = true} (which is the default)
+	 * then simply File#createNewFile() is called.
+	 *
+	 * But if {@code core.supportsAtomicCreateNewFile = false} then after
+	 * successful creation of the lock file a hardlink to that lock file is
+	 * created and the attribute nlink of the lock file is checked to be 2. If
+	 * multiple clients manage to create the same lock file nlink would be
+	 * greater than 2 showing the error.
+	 *
+	 * @see https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html
+	 * @since 4.5
+	 */
+	public boolean createNewFile(File lock) throws IOException {
+		if (!lock.createNewFile()) {
+			return false;
+		}
+		if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
+			return true;
+		}
+		Path lockPath = lock.toPath();
+		Path link = Files.createLink(Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
+				lockPath);
+		try {
+			Integer nlink = (Integer) (Files.getAttribute(lockPath,
+					"unix:nlink")); //$NON-NLS-1$
+			if (nlink != 2) {
+				LOG.warn("nlink of link to lock file {0} was not 2 but {1}", //$NON-NLS-1$
+						lock.getPath(), nlink);
+				return false;
+			}
+			return true;
+		} catch (UnsupportedOperationException | IllegalArgumentException e) {
+			supportsUnixNLink = false;
+			return true;
+		} finally {
+			Files.delete(link);
+		}
+	}
 }