diff options
7 files changed, 176 insertions, 63 deletions
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters deleted file mode 100644 index a70ce77e1b..0000000000 --- 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 20d08c3a00..894341e5c6 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,50 +1,49 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <component id="org.eclipse.jgit" version="2"> - <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"> + <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.ReflogEntry"/> - <message_argument value="PREFIX_CREATED"/> + <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> + <message_argument value="CONFIG_KEY_IN_CORE_LIMIT"/> </message_arguments> </filter> - <filter comment="adding enum constant does not break binary compatibility" id="403767336"> + <filter id="336658481"> <message_arguments> - <message_argument value="org.eclipse.jgit.lib.ReflogEntry"/> - <message_argument value="PREFIX_FAST_FORWARD"/> + <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> + <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/> </message_arguments> </filter> - <filter comment="adding enum constant does not break binary compatibility" id="403767336"> + <filter id="336658481"> <message_arguments> - <message_argument value="org.eclipse.jgit.lib.ReflogEntry"/> - <message_argument value="PREFIX_FORCED_UPDATE"/> + <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> + <message_argument value="CONFIG_MERGE_SECTION"/> </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"> + <filter id="1141899266"> <message_arguments> - <message_argument value="org.eclipse.jgit.merge.MergeStrategy"/> - <message_argument value="newMerger(ObjectInserter, Config)"/> + <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/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"> + <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> - <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/> - <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/> + <message_argument value="org.eclipse.jgit.lib.ReflogEntry"/> + <message_argument value="PREFIX_CREATED"/> </message_arguments> </filter> - <filter id="1141899266"> + <filter comment="adding enum constant does not break binary compatibility" id="403767336"> <message_arguments> - <message_argument value="3.5"/> - <message_argument value="4.9"/> - <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/> + <message_argument value="org.eclipse.jgit.lib.ReflogEntry"/> + <message_argument value="PREFIX_FAST_FORWARD"/> </message_arguments> </filter> - <filter id="1143996420"> + <filter comment="adding enum constant does not break binary compatibility" id="403767336"> <message_arguments> - <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/> + <message_argument value="org.eclipse.jgit.lib.ReflogEntry"/> + <message_argument value="PREFIX_FORCED_UPDATE"/> </message_arguments> </filter> </resource> @@ -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 6221cfa4fc..9b1fe8ce9f 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 class LockFile { */ 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 bb1dc91cdb..a44acacd12 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.MissingObjectException; 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 @@ public class RefDirectory extends RefDatabase { } 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 ad5b106aa2..08c883a83e 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 1cc39bd46c..3e0b41af34 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 @@ public abstract class FS { 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 abstract class FS { } /** + * 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 2d58a0241c..da3b0572cc 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.io.PrintStream; 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 java.util.Set; 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 @@ public class FS_POSIX extends FS { } } + @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 class FS_POSIX extends FS { 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); + } + } } |