@ -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 ) ;
}
}
}