Merge branch 'stable-5.0' into stable-5.1
* stable-5.0: 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: I3c8892e33516bdcadd983e7ab668635b7fa3d4c5 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
commit
582d623964
|
@ -1,16 +1,28 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<component id="org.eclipse.jgit" version="2">
|
<component id="org.eclipse.jgit" version="2">
|
||||||
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
|
<resource path="META-INF/MANIFEST.MF">
|
||||||
<filter id="337768515">
|
<filter id="924844039">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
<message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
|
<message_argument value="5.1.0"/>
|
||||||
|
<message_argument value="5.1.0"/>
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</resource>
|
||||||
<resource path="src/org/eclipse/jgit/transport/GitProtocolConstants.java" type="org.eclipse.jgit.transport.GitProtocolConstants">
|
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
|
||||||
<filter id="337768515">
|
<filter id="1141899266">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
<message_argument value="org.eclipse.jgit.transport.GitProtocolConstants"/>
|
<message_argument value="4.7"/>
|
||||||
|
<message_argument value="5.1"/>
|
||||||
|
<message_argument value="createNewFileAtomic(File)"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
|
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken">
|
||||||
|
<filter id="1141899266">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="4.7"/>
|
||||||
|
<message_argument value="5.1"/>
|
||||||
|
<message_argument value="LockToken"/>
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</resource>
|
||||||
|
|
|
@ -125,6 +125,7 @@ checkoutUnexpectedResult=Checkout returned unexpected result {0}
|
||||||
classCastNotA=Not a {0}
|
classCastNotA=Not a {0}
|
||||||
cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
|
cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
|
||||||
closed=closed
|
closed=closed
|
||||||
|
closeLockTokenFailed=Closing LockToken ''{0}'' failed
|
||||||
collisionOn=Collision on {0}
|
collisionOn=Collision on {0}
|
||||||
commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
|
commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
|
||||||
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
|
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
|
||||||
|
@ -300,6 +301,7 @@ expectedLessThanGot=expected less than ''{0}'', got ''{1}''
|
||||||
expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}''
|
expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}''
|
||||||
expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
|
expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
|
||||||
expectedReportForRefNotReceived={0}: expected report for ref {1} not received
|
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
|
failedToDetermineFilterDefinition=An exception occured while determining filter definitions
|
||||||
failedUpdatingRefs=failed updating refs
|
failedUpdatingRefs=failed updating refs
|
||||||
failureDueToOneOfTheFollowing=Failure due to one of the following:
|
failureDueToOneOfTheFollowing=Failure due to one of the following:
|
||||||
|
@ -734,6 +736,7 @@ unknownRepositoryFormat=Unknown repository format
|
||||||
unknownRepositoryFormat2=Unknown repository format "{0}"; expected "0".
|
unknownRepositoryFormat2=Unknown repository format "{0}"; expected "0".
|
||||||
unknownTransportCommand=unknown command {0}
|
unknownTransportCommand=unknown command {0}
|
||||||
unknownZlibError=Unknown zlib error.
|
unknownZlibError=Unknown zlib error.
|
||||||
|
unlockLockFileFailed=Unlocking LockFile ''{0}'' failed
|
||||||
unmergedPath=Unmerged path: {0}
|
unmergedPath=Unmerged path: {0}
|
||||||
unmergedPaths=Repository contains unmerged paths
|
unmergedPaths=Repository contains unmerged paths
|
||||||
unpackException=Exception while parsing pack stream
|
unpackException=Exception while parsing pack stream
|
||||||
|
|
|
@ -185,6 +185,7 @@ public static JGitText get() {
|
||||||
/***/ public String checkoutUnexpectedResult;
|
/***/ public String checkoutUnexpectedResult;
|
||||||
/***/ public String classCastNotA;
|
/***/ public String classCastNotA;
|
||||||
/***/ public String cloneNonEmptyDirectory;
|
/***/ public String cloneNonEmptyDirectory;
|
||||||
|
/***/ public String closeLockTokenFailed;
|
||||||
/***/ public String closed;
|
/***/ public String closed;
|
||||||
/***/ public String collisionOn;
|
/***/ public String collisionOn;
|
||||||
/***/ public String commandClosedStderrButDidntExit;
|
/***/ public String commandClosedStderrButDidntExit;
|
||||||
|
@ -361,6 +362,7 @@ public static JGitText get() {
|
||||||
/***/ public String expectedPktLineWithService;
|
/***/ public String expectedPktLineWithService;
|
||||||
/***/ public String expectedReceivedContentType;
|
/***/ public String expectedReceivedContentType;
|
||||||
/***/ public String expectedReportForRefNotReceived;
|
/***/ public String expectedReportForRefNotReceived;
|
||||||
|
/***/ public String failedAtomicFileCreation;
|
||||||
/***/ public String failedToDetermineFilterDefinition;
|
/***/ public String failedToDetermineFilterDefinition;
|
||||||
/***/ public String failedUpdatingRefs;
|
/***/ public String failedUpdatingRefs;
|
||||||
/***/ public String failureDueToOneOfTheFollowing;
|
/***/ public String failureDueToOneOfTheFollowing;
|
||||||
|
@ -795,6 +797,7 @@ public static JGitText get() {
|
||||||
/***/ public String unknownRepositoryFormat2;
|
/***/ public String unknownRepositoryFormat2;
|
||||||
/***/ public String unknownTransportCommand;
|
/***/ public String unknownTransportCommand;
|
||||||
/***/ public String unknownZlibError;
|
/***/ public String unknownZlibError;
|
||||||
|
/***/ public String unlockLockFileFailed;
|
||||||
/***/ public String unmergedPath;
|
/***/ public String unmergedPath;
|
||||||
/***/ public String unmergedPaths;
|
/***/ public String unmergedPaths;
|
||||||
/***/ public String unpackException;
|
/***/ public String unpackException;
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -948,6 +949,8 @@ private boolean isDirectory(Path p) {
|
||||||
private void delete(Path d) {
|
private void delete(Path d) {
|
||||||
try {
|
try {
|
||||||
Files.delete(d);
|
Files.delete(d);
|
||||||
|
} catch (DirectoryNotEmptyException e) {
|
||||||
|
// Don't log
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error(MessageFormat.format(JGitText.get().cannotDeleteFile, d),
|
LOG.error(MessageFormat.format(JGitText.get().cannotDeleteFile, d),
|
||||||
e);
|
e);
|
||||||
|
|
|
@ -63,7 +63,10 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FS.LockToken;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git style file locking and replacement.
|
* Git style file locking and replacement.
|
||||||
|
@ -76,6 +79,7 @@
|
||||||
* name.
|
* name.
|
||||||
*/
|
*/
|
||||||
public class LockFile {
|
public class LockFile {
|
||||||
|
private final static Logger LOG = LoggerFactory.getLogger(LockFile.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock the given file.
|
* Unlock the given file.
|
||||||
|
@ -133,6 +137,8 @@ public boolean accept(File dir, String name) {
|
||||||
|
|
||||||
private FileSnapshot commitSnapshot;
|
private FileSnapshot commitSnapshot;
|
||||||
|
|
||||||
|
private LockToken token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new lock for any file.
|
* Create a new lock for any file.
|
||||||
*
|
*
|
||||||
|
@ -155,7 +161,8 @@ public LockFile(File f) {
|
||||||
*/
|
*/
|
||||||
public boolean lock() throws IOException {
|
public boolean lock() throws IOException {
|
||||||
FileUtils.mkdirs(lck.getParentFile(), true);
|
FileUtils.mkdirs(lck.getParentFile(), true);
|
||||||
if (FS.DETECTED.createNewFile(lck)) {
|
token = FS.DETECTED.createNewFileAtomic(lck);
|
||||||
|
if (token.isCreated()) {
|
||||||
haveLck = true;
|
haveLck = true;
|
||||||
try {
|
try {
|
||||||
os = new FileOutputStream(lck);
|
os = new FileOutputStream(lck);
|
||||||
|
@ -163,6 +170,8 @@ public boolean lock() throws IOException {
|
||||||
unlock();
|
unlock();
|
||||||
throw ioe;
|
throw ioe;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
closeToken();
|
||||||
}
|
}
|
||||||
return haveLck;
|
return haveLck;
|
||||||
}
|
}
|
||||||
|
@ -441,6 +450,7 @@ public boolean commit() {
|
||||||
try {
|
try {
|
||||||
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
|
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
|
||||||
haveLck = false;
|
haveLck = false;
|
||||||
|
closeToken();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
unlock();
|
unlock();
|
||||||
|
@ -448,6 +458,13 @@ public boolean commit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void closeToken() {
|
||||||
|
if (token != null) {
|
||||||
|
token.close();
|
||||||
|
token = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void saveStatInformation() {
|
private void saveStatInformation() {
|
||||||
if (needSnapshot)
|
if (needSnapshot)
|
||||||
commitSnapshot = FileSnapshot.save(lck);
|
commitSnapshot = FileSnapshot.save(lck);
|
||||||
|
@ -490,8 +507,9 @@ public void unlock() {
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
try {
|
try {
|
||||||
os.close();
|
os.close();
|
||||||
} catch (IOException ioe) {
|
} catch (IOException e) {
|
||||||
// Ignore this
|
LOG.error(MessageFormat
|
||||||
|
.format(JGitText.get().unlockLockFileFailed, lck), e);
|
||||||
}
|
}
|
||||||
os = null;
|
os = null;
|
||||||
}
|
}
|
||||||
|
@ -501,7 +519,10 @@ public void unlock() {
|
||||||
try {
|
try {
|
||||||
FileUtils.delete(lck, FileUtils.RETRY);
|
FileUtils.delete(lck, FileUtils.RETRY);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// couldn't delete the file even after retry.
|
LOG.error(MessageFormat
|
||||||
|
.format(JGitText.get().unlockLockFileFailed, lck), e);
|
||||||
|
} finally {
|
||||||
|
closeToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -712,7 +712,7 @@ public static byte[] encode(String str) {
|
||||||
/**
|
/**
|
||||||
* Suffix of lock file name
|
* Suffix of lock file name
|
||||||
*
|
*
|
||||||
* @since 5.0
|
* @since 4.8
|
||||||
*/
|
*/
|
||||||
public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$
|
public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -52,6 +53,8 @@
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
@ -59,6 +62,7 @@
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -873,12 +877,77 @@ public void createSymLink(File path, String target) throws IOException {
|
||||||
* @return <code>true</code> if the file was created, <code>false</code> if
|
* @return <code>true</code> if the file was created, <code>false</code> if
|
||||||
* the file already existed
|
* the file already existed
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
|
* @deprecated use {@link #createNewFileAtomic(File)} instead
|
||||||
* @since 4.5
|
* @since 4.5
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean createNewFile(File path) throws IOException {
|
public boolean createNewFile(File path) throws IOException {
|
||||||
return path.createNewFile();
|
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
|
* See
|
||||||
* {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
|
* {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
|
||||||
|
|
|
@ -52,14 +52,19 @@
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
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.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.errors.CommandFailedException;
|
import org.eclipse.jgit.errors.CommandFailedException;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
@ -372,9 +377,12 @@ public boolean supportsAtomicCreateNewFile() {
|
||||||
* multiple clients manage to create the same lock file nlink would be
|
* multiple clients manage to create the same lock file nlink would be
|
||||||
* greater than 2 showing the error.
|
* 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
|
* @since 4.5
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean createNewFile(File lock) throws IOException {
|
public boolean createNewFile(File lock) throws IOException {
|
||||||
if (!lock.createNewFile()) {
|
if (!lock.createNewFile()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -383,22 +391,94 @@ public boolean createNewFile(File lock) throws IOException {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Path lockPath = lock.toPath();
|
Path lockPath = lock.toPath();
|
||||||
Path link = Files.createLink(Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
|
Path link = null;
|
||||||
lockPath);
|
|
||||||
try {
|
try {
|
||||||
|
link = Files.createLink(
|
||||||
|
Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
|
||||||
|
lockPath);
|
||||||
Integer nlink = (Integer) (Files.getAttribute(lockPath,
|
Integer nlink = (Integer) (Files.getAttribute(lockPath,
|
||||||
"unix:nlink")); //$NON-NLS-1$
|
"unix:nlink")); //$NON-NLS-1$
|
||||||
if (nlink != 2) {
|
if (nlink > 2) {
|
||||||
LOG.warn("nlink of link to lock file {} was not 2 but {}", //$NON-NLS-1$
|
LOG.warn("nlink of link to lock file {} was not 2 but {}", //$NON-NLS-1$
|
||||||
lock.getPath(), nlink);
|
lock.getPath(), nlink);
|
||||||
return false;
|
return false;
|
||||||
|
} else if (nlink < 2) {
|
||||||
|
supportsUnixNLink = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (UnsupportedOperationException | IllegalArgumentException e) {
|
} catch (UnsupportedOperationException | IllegalArgumentException e) {
|
||||||
supportsUnixNLink = false;
|
supportsUnixNLink = false;
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
|
if (link != null) {
|
||||||
Files.delete(link);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue