summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2018-09-08 10:05:36 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2018-09-08 10:12:48 +0200
commit5a583ef39357bde7700882f6d6ad72a8f3af8603 (patch)
tree06a6dbcd94306f640e829a88fcffd1192a327ecf
parentb67aacda2fcf2f229e80a3eb2c7aabfcb0b6a51b (diff)
parent9e26cb106fad40f26056f91e352f40239eacb213 (diff)
downloadjgit-5a583ef39357bde7700882f6d6ad72a8f3af8603.tar.gz
jgit-5a583ef39357bde7700882f6d6ad72a8f3af8603.zip
Merge branch 'stable-4.10' into stable-4.11
* stable-4.10: Fix atomic lock file creation on NFS Use constant for ".lock" Fix handling of option core.supportsAtomicCreateNewFile GC: Avoid logging errors when deleting non-empty folders Change-Id: Ie86079d9ad76972306bc80e63d8bfe18ae06a0da Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.jgit.lfs/.settings/.api_filters27
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java9
-rw-r--r--org.eclipse.jgit/.settings/.api_filters65
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java90
13 files changed, 232 insertions, 98 deletions
diff --git a/org.eclipse.jgit.lfs/.settings/.api_filters b/org.eclipse.jgit.lfs/.settings/.api_filters
deleted file mode 100644
index f4887e272b..0000000000
--- a/org.eclipse.jgit.lfs/.settings/.api_filters
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.lfs" version="2">
- <resource path="src/org/eclipse/jgit/lfs/CleanFilter.java" type="org.eclipse.jgit.lfs.CleanFilter">
- <filter id="421572723">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lfs.CleanFilter"/>
- <message_argument value="register()"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lfs/LfsPointer.java" type="org.eclipse.jgit.lfs.LfsPointer">
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lfs.LfsPointer"/>
- <message_argument value="SIZE_THRESHOLD"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lfs/SmudgeFilter.java" type="org.eclipse.jgit.lfs.SmudgeFilter">
- <filter id="421572723">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lfs.SmudgeFilter"/>
- <message_argument value="register()"/>
- </message_arguments>
- </filter>
- </resource>
-</component>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 52861ecd53..d8d45a85b1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -47,6 +47,7 @@ package org.eclipse.jgit.internal.storage.file;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.junit.Assert.assertEquals;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -819,11 +820,11 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
// Check that the involved refs are the same despite the failure
assertExists(false, toName);
if (!toLock.equals(toName))
- assertExists(false, toName + ".lock");
- assertExists(true, toLock + ".lock");
+ assertExists(false, toName + LOCK_SUFFIX);
+ assertExists(true, toLock + LOCK_SUFFIX);
if (!toLock.equals(fromName))
- assertExists(false, "logs/" + fromName + ".lock");
- assertExists(false, "logs/" + toName + ".lock");
+ assertExists(false, "logs/" + fromName + LOCK_SUFFIX);
+ assertExists(false, "logs/" + toName + LOCK_SUFFIX);
assertEquals(oldHeadId, db.resolve(Constants.HEAD));
assertEquals(oldfromId, db.resolve(fromName));
assertNull(db.resolve(toName));
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 67d731bd70..48051e14fb 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,70 +3,35 @@
<resource path="META-INF/MANIFEST.MF">
<filter id="924844039">
<message_arguments>
- <message_argument value="4.11.1"/>
- <message_argument value="4.11.0"/>
- </message_arguments>
- </filter>
- <filter id="924844039">
- <message_arguments>
<message_argument value="4.11.2"/>
<message_argument value="4.11.0"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/diff/DiffEntry.java" type="org.eclipse.jgit.diff.DiffEntry">
- <filter id="336658481">
+ <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
+ <filter id="1141899266">
<message_arguments>
- <message_argument value="org.eclipse.jgit.diff.DiffEntry"/>
- <message_argument value="diffAttribute"/>
+ <message_argument value="4.8"/>
+ <message_argument value="4.11"/>
+ <message_argument value="LOCK_SUFFIX"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
- <filter id="336658481">
+ <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
+ <filter id="1141899266">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
- <message_argument value="CONFIG_KEY_REQUIRED"/>
- </message_arguments>
- </filter>
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
- <message_argument value="CONFIG_SECTION_LFS"/>
+ <message_argument value="4.7"/>
+ <message_argument value="4.11"/>
+ <message_argument value="createNewFileAtomic(File)"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
- <message_argument value="workingTreeOptions"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/storage/pack/PackStatistics.java" type="org.eclipse.jgit.storage.pack.PackStatistics$Accumulator">
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
- <message_argument value="advertised"/>
- </message_arguments>
- </filter>
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
- <message_argument value="haves"/>
- </message_arguments>
- </filter>
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
- <message_argument value="timeNegotiating"/>
- </message_arguments>
- </filter>
- <filter id="336658481">
+ <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken">
+ <filter id="1141899266">
<message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
- <message_argument value="wants"/>
+ <message_argument value="4.7"/>
+ <message_argument value="4.11"/>
+ <message_argument value="LockToken"/>
</message_arguments>
</filter>
</resource>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 0d8ba01e22..3f845556e1 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -125,6 +125,7 @@ checkoutUnexpectedResult=Checkout returned unexpected result {0}
classCastNotA=Not a {0}
cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
closed=closed
+closeLockTokenFailed=Closing LockToken ''{0}'' failed
collisionOn=Collision on {0}
commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
@@ -298,6 +299,7 @@ expectedLessThanGot=expected less than ''{0}'', got ''{1}''
expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}''
expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
expectedReportForRefNotReceived={0}: expected report for ref {1} not received
+failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}"
failedToDetermineFilterDefinition=An exception occured while determining filter definitions
failedUpdatingRefs=failed updating refs
failureDueToOneOfTheFollowing=Failure due to one of the following:
@@ -722,6 +724,7 @@ unknownObjectType2=unknown
unknownRepositoryFormat=Unknown repository format
unknownRepositoryFormat2=Unknown repository format "{0}"; expected "0".
unknownZlibError=Unknown zlib error.
+unlockLockFileFailed=Unlocking LockFile ''{0}'' failed
unmergedPath=Unmerged path: {0}
unmergedPaths=Repository contains unmerged paths
unpackException=Exception while parsing pack stream
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 4e947f8f01..d3514344d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -185,6 +185,7 @@ public class JGitText extends TranslationBundle {
/***/ public String checkoutUnexpectedResult;
/***/ public String classCastNotA;
/***/ public String cloneNonEmptyDirectory;
+ /***/ public String closeLockTokenFailed;
/***/ public String closed;
/***/ public String collisionOn;
/***/ public String commandClosedStderrButDidntExit;
@@ -359,6 +360,7 @@ public class JGitText extends TranslationBundle {
/***/ public String expectedPktLineWithService;
/***/ public String expectedReceivedContentType;
/***/ public String expectedReportForRefNotReceived;
+ /***/ public String failedAtomicFileCreation;
/***/ public String failedToDetermineFilterDefinition;
/***/ public String failedUpdatingRefs;
/***/ public String failureDueToOneOfTheFollowing;
@@ -783,6 +785,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unknownRepositoryFormat;
/***/ public String unknownRepositoryFormat2;
/***/ public String unknownZlibError;
+ /***/ public String unlockLockFileFailed;
/***/ public String unmergedPath;
/***/ public String unmergedPaths;
/***/ public String unpackException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 767fffcf0f..ad41d98860 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -54,6 +54,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -946,6 +947,8 @@ public class GC {
private void delete(Path d) {
try {
Files.delete(d);
+ } catch (DirectoryNotEmptyException e) {
+ // Don't log
} catch (IOException e) {
LOG.error(MessageFormat.format(JGitText.get().cannotDeleteFile, d),
e);
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 bc23dcce27..9befe2ad58 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
@@ -44,6 +44,8 @@
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -61,7 +63,10 @@ import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.LockToken;
import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Git style file locking and replacement.
@@ -74,7 +79,7 @@ import org.eclipse.jgit.util.FileUtils;
* name.
*/
public class LockFile {
- static final String SUFFIX = ".lock"; //$NON-NLS-1$
+ private final static Logger LOG = LoggerFactory.getLogger(LockFile.class);
/**
* Unlock the given file.
@@ -106,14 +111,15 @@ public class LockFile {
* @return lock file
*/
static File getLockFile(File file) {
- return new File(file.getParentFile(), file.getName() + SUFFIX);
+ return new File(file.getParentFile(),
+ file.getName() + LOCK_SUFFIX);
}
/** Filter to skip over active lock files when listing a directory. */
static final FilenameFilter FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
- return !name.endsWith(SUFFIX);
+ return !name.endsWith(LOCK_SUFFIX);
}
};
@@ -131,6 +137,8 @@ public class LockFile {
private FileSnapshot commitSnapshot;
+ private LockToken token;
+
/**
* Create a new lock for any file.
*
@@ -171,7 +179,8 @@ public class LockFile {
*/
public boolean lock() throws IOException {
FileUtils.mkdirs(lck.getParentFile(), true);
- if (FS.DETECTED.createNewFile(lck)) {
+ token = FS.DETECTED.createNewFileAtomic(lck);
+ if (token.isCreated()) {
haveLck = true;
try {
os = new FileOutputStream(lck);
@@ -179,6 +188,8 @@ public class LockFile {
unlock();
throw ioe;
}
+ } else {
+ closeToken();
}
return haveLck;
}
@@ -457,6 +468,7 @@ public class LockFile {
try {
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
haveLck = false;
+ closeToken();
return true;
} catch (IOException e) {
unlock();
@@ -464,6 +476,13 @@ public class LockFile {
}
}
+ private void closeToken() {
+ if (token != null) {
+ token.close();
+ token = null;
+ }
+ }
+
private void saveStatInformation() {
if (needSnapshot)
commitSnapshot = FileSnapshot.save(lck);
@@ -506,8 +525,9 @@ public class LockFile {
if (os != null) {
try {
os.close();
- } catch (IOException ioe) {
- // Ignore this
+ } catch (IOException e) {
+ LOG.error(MessageFormat
+ .format(JGitText.get().unlockLockFileFailed, lck), e);
}
os = null;
}
@@ -517,7 +537,10 @@ public class LockFile {
try {
FileUtils.delete(lck, FileUtils.RETRY);
} catch (IOException e) {
- // couldn't delete the file even after retry.
+ LOG.error(MessageFormat
+ .format(JGitText.get().unlockLockFileFailed, lck), e);
+ } finally {
+ closeToken();
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
index d99c266144..fd6b427935 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
@@ -46,6 +46,7 @@
package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Constants.R_NOTES;
import static org.eclipse.jgit.lib.Constants.R_REFS;
@@ -84,7 +85,7 @@ public class ReflogWriter {
* @return the name of the ref's lock ref.
*/
public static String refLockFor(String name) {
- return name + LockFile.SUFFIX;
+ return name + LOCK_SUFFIX;
}
private final RefDirectory refdb;
@@ -266,4 +267,4 @@ public class ReflogWriter {
|| refName.startsWith(R_REMOTES)
|| refName.startsWith(R_NOTES);
}
-}
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index bb85229f82..fe9f8e9073 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -688,6 +688,13 @@ public final class Constants {
public static final ObjectId EMPTY_BLOB_ID = ObjectId
.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
+ /**
+ * Suffix of lock file name
+ *
+ * @since 4.8
+ */
+ public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$
+
private Constants() {
// Hide the default constructor
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 4c0bde1376..db5cad081b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -48,6 +48,8 @@
package org.eclipse.jgit.lib;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -1378,10 +1380,12 @@ public abstract class Repository implements AutoCloseable {
*/
public static boolean isValidRefName(final String refName) {
final int len = refName.length();
- if (len == 0)
+ if (len == 0) {
return false;
- if (refName.endsWith(".lock")) //$NON-NLS-1$
+ }
+ if (refName.endsWith(LOCK_SUFFIX)) {
return false;
+ }
// Refs may be stored as loose files so invalid paths
// on the local system must also be invalid refs.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index 09cd67b515..39f9897ffe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -43,6 +43,8 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -344,7 +346,7 @@ public class TransportSftp extends SshTransport implements WalkTransport {
@Override
void writeFile(final String path, final byte[] data) throws IOException {
- final String lock = path + ".lock"; //$NON-NLS-1$
+ final String lock = path + LOCK_SUFFIX;
try {
super.writeFile(lock, data);
try {
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 edcb9d7a6a..e7126f23e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -45,6 +45,7 @@ package org.eclipse.jgit.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -52,6 +53,8 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
@@ -59,6 +62,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -862,13 +866,78 @@ public abstract class FS {
* @return <code>true</code> if the file was created, <code>false</code> if
* the file already existed
* @throws java.io.IOException
+ * @deprecated use {@link #createNewFileAtomic(File)} instead
* @since 4.5
*/
+ @Deprecated
public boolean createNewFile(File path) throws IOException {
return path.createNewFile();
}
/**
+ * A token representing a file created by
+ * {@link #createNewFileAtomic(File)}. The token must be retained until the
+ * file has been deleted in order to guarantee that the unique file was
+ * created atomically. As soon as the file is no longer needed the lock
+ * token must be closed.
+ *
+ * @since 4.7
+ */
+ public static class LockToken implements Closeable {
+ private boolean isCreated;
+
+ private Optional<Path> link;
+
+ LockToken(boolean isCreated, Optional<Path> link) {
+ this.isCreated = isCreated;
+ this.link = link;
+ }
+
+ /**
+ * @return {@code true} if the file was created successfully
+ */
+ public boolean isCreated() {
+ return isCreated;
+ }
+
+ @Override
+ public void close() {
+ if (link.isPresent()) {
+ try {
+ Files.delete(link.get());
+ } catch (IOException e) {
+ LOG.error(MessageFormat.format(JGitText.get().closeLockTokenFailed,
+ this), e);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
+ ", link=" //$NON-NLS-1$
+ + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
+ : "<null>]"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Create a new file. See {@link java.io.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 LockToken this token must be closed after the created file was
+ * deleted
+ * @throws IOException
+ * @since 4.7
+ */
+ public LockToken createNewFileAtomic(File path) throws IOException {
+ return new LockToken(path.createNewFile(), Optional.empty());
+ }
+
+ /**
* See
* {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
*
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 a2923db9dd..45237c42be 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
@@ -52,14 +52,19 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
@@ -372,9 +377,12 @@ public class FS_POSIX extends FS {
* 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
+ * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html"
+ *
+ * @deprecated use {@link FS_POSIX#createNewFileAtomic(File)} instead
* @since 4.5
*/
+ @Deprecated
public boolean createNewFile(File lock) throws IOException {
if (!lock.createNewFile()) {
return false;
@@ -383,22 +391,94 @@ public class FS_POSIX extends FS {
return true;
}
Path lockPath = lock.toPath();
- Path link = Files.createLink(Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
- lockPath);
+ Path link = null;
try {
+ link = Files.createLink(
+ Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
+ lockPath);
Integer nlink = (Integer) (Files.getAttribute(lockPath,
"unix:nlink")); //$NON-NLS-1$
- if (nlink != 2) {
+ 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;
+ } else if (nlink < 2) {
+ supportsUnixNLink = false;
}
return true;
} catch (UnsupportedOperationException | IllegalArgumentException e) {
supportsUnixNLink = false;
return true;
} finally {
- Files.delete(link);
+ if (link != null) {
+ Files.delete(link);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * An implementation of the File#createNewFile() semantics which can create
+ * a unique file atomically 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 hard link 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. The hard link needs to be retained
+ * until the corresponding file is no longer needed in order to prevent that
+ * another process can create the same file concurrently using another NFS
+ * client which might not yet see the file due to caching.
+ *
+ * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html"
+ * @param file
+ * the unique file to be created atomically
+ * @return LockToken this lock token must be held until the file is no
+ * longer needed
+ * @throws IOException
+ * @since 5.0
+ */
+ @Override
+ public LockToken createNewFileAtomic(File file) throws IOException {
+ if (!file.createNewFile()) {
+ return token(false, null);
+ }
+ if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
+ return token(true, null);
}
+ Path link = null;
+ Path path = file.toPath();
+ try {
+ link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
+ Integer nlink = (Integer) (Files.getAttribute(path,
+ "unix:nlink")); //$NON-NLS-1$
+ if (nlink.intValue() > 2) {
+ LOG.warn(MessageFormat.format(
+ JGitText.get().failedAtomicFileCreation, path, nlink));
+ return token(false, link);
+ } else if (nlink.intValue() < 2) {
+ supportsUnixNLink = false;
+ }
+ return token(true, link);
+ } catch (UnsupportedOperationException | IllegalArgumentException e) {
+ supportsUnixNLink = false;
+ return token(true, link);
+ }
+ }
+
+ private static LockToken token(boolean created, @Nullable Path p) {
+ return ((p != null) && Files.exists(p))
+ ? new LockToken(created, Optional.of(p))
+ : new LockToken(created, Optional.empty());
+ }
+
+ private static String uniqueLinkPath(File file) {
+ UUID id = UUID.randomUUID();
+ return file.getAbsolutePath() + "." //$NON-NLS-1$
+ + Long.toHexString(id.getMostSignificantBits())
+ + Long.toHexString(id.getLeastSignificantBits());
}
}