mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-201078 Copying absolute symlinks to remote Eel
GitOrigin-RevId: 0afb9b3c0c334c17541832038db67c120b9cfa66
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a795a03d49
commit
e99eeb84c1
+87
-47
@@ -570,10 +570,16 @@ object EelPathUtils {
|
||||
@RequiresBackgroundThread
|
||||
@RequiresBlockingContext
|
||||
@VisibleForTesting
|
||||
fun walkingTransfer(sourceRoot: Path, targetRoot: Path, removeSource: Boolean, fileAttributesStrategy: FileTransferAttributesStrategy) {
|
||||
fun walkingTransfer(
|
||||
sourceRoot: Path,
|
||||
targetRoot: Path,
|
||||
removeSource: Boolean,
|
||||
fileAttributesStrategy: FileTransferAttributesStrategy,
|
||||
absoluteSymlinkHandler: IncrementalWalkingTransferAbsoluteSymlinkHandler? = null,
|
||||
) {
|
||||
if (Registry.`is`("ijent.incremental.walking.transfer") && !removeSource) {
|
||||
runBlockingMaybeCancellable {
|
||||
incrementalWalkingTransfer(sourceRoot, targetRoot, fileAttributesStrategy)
|
||||
incrementalWalkingTransfer(sourceRoot, targetRoot, fileAttributesStrategy, absoluteSymlinkHandler)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -698,7 +704,7 @@ object EelPathUtils {
|
||||
/**
|
||||
* Function only checks permissions, and it ignores the owner, group, sticky bit, gid, and uid.
|
||||
* If FileTransferAttributesStrategy is RequirePosixPermissions, it will be checked if remote file permissions contain required permissions.
|
||||
**/
|
||||
**/
|
||||
private fun arePermissionsEqual(fileAttributesStrategy: FileTransferAttributesStrategy, local: WalkDirectoryEntry.Permissions, remote: WalkDirectoryEntry.Permissions): Boolean {
|
||||
return when (local) {
|
||||
is WalkDirectoryEntryPosix.Permissions -> {
|
||||
@@ -797,7 +803,7 @@ object EelPathUtils {
|
||||
) : DiffOperation()
|
||||
|
||||
// If a file has the same local and remote path but different file type, the existing one is deleted and replaced with a correct one.
|
||||
// ReplaceFile is additionally used in case of symlinks. The symlink target cannot be changed in place, thus it requires replacing.
|
||||
// ReplaceFile is additionally used in the case of a relative symlink. The symlink target cannot be changed in place, thus it requires replacing.
|
||||
// Always syncs permission, attributes and timestamps.
|
||||
data class ReplaceFile(
|
||||
val localFile: WalkDirectoryEntry,
|
||||
@@ -812,6 +818,12 @@ object EelPathUtils {
|
||||
val localFile: WalkDirectoryEntry,
|
||||
val remoteFile: WalkDirectoryEntry,
|
||||
) : DiffOperation()
|
||||
|
||||
// Absolute symlinks are left untouched, and it is up to the user-provided lambda to handle it
|
||||
data class AbsoluteSymlink(
|
||||
val sourceSymlink: WalkDirectoryEntry,
|
||||
val remoteSymlink: WalkDirectoryEntry?,
|
||||
) : DiffOperation()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -888,7 +900,10 @@ object EelPathUtils {
|
||||
|
||||
// if there is a file locally but not on the remote side - the file was created
|
||||
if (localEntry != null && remoteEntry == null) {
|
||||
if (localEntry.type !is WalkDirectoryEntry.Type.Other) {
|
||||
if (localEntry.type is WalkDirectoryEntry.Type.Symlink.Absolute) {
|
||||
emit(DiffOperation.AbsoluteSymlink(localEntry, null))
|
||||
}
|
||||
else if (localEntry.type !is WalkDirectoryEntry.Type.Other) {
|
||||
emit(DiffOperation.Create(localEntry))
|
||||
}
|
||||
localEntry = null
|
||||
@@ -956,7 +971,8 @@ object EelPathUtils {
|
||||
}
|
||||
}
|
||||
is WalkDirectoryEntry.Type.Symlink.Absolute -> {
|
||||
// TODO: IJPL-201078
|
||||
emit(DiffOperation.AbsoluteSymlink(localEntry, remoteEntry))
|
||||
opEmitted = true
|
||||
}
|
||||
is WalkDirectoryEntry.Type.Other -> {
|
||||
// other file types have been handled prior to this when
|
||||
@@ -982,7 +998,10 @@ object EelPathUtils {
|
||||
}
|
||||
// if the local path is in lower lexicographical order than the remote path, it means that the local file was created
|
||||
else if (pathComparison < 0) {
|
||||
if (localEntry !is WalkDirectoryEntry.Type.Symlink.Absolute) { // TODO: IJPL-201078
|
||||
if (localEntry is WalkDirectoryEntry.Type.Symlink.Absolute) {
|
||||
emit(DiffOperation.AbsoluteSymlink(localEntry, null))
|
||||
}
|
||||
else {
|
||||
emit(DiffOperation.Create(localEntry))
|
||||
}
|
||||
localEntry = null
|
||||
@@ -1056,7 +1075,7 @@ object EelPathUtils {
|
||||
}
|
||||
|
||||
if (setAttributes) {
|
||||
if (localEntry.permissions == null) {
|
||||
if (localEntry.attributes == null) {
|
||||
error("Attributes are supposed to be transferred, but were not yielded")
|
||||
}
|
||||
remoteEntry.fileAttributesViewOrNull<DosFileAttributeView>(LinkOption.NOFOLLOW_LINKS)?.let { remoteView ->
|
||||
@@ -1108,29 +1127,6 @@ object EelPathUtils {
|
||||
scope: CoroutineScope,
|
||||
) {
|
||||
coroutineScope {
|
||||
|
||||
val attrs = sourceRoot.asNioPath().fileAttributesView<BasicFileAttributeView>(LinkOption.NOFOLLOW_LINKS).readAttributes()
|
||||
when {
|
||||
attrs.isDirectory -> {
|
||||
if (!targetRoot.asNioPath().isDirectory(LinkOption.NOFOLLOW_LINKS)) {
|
||||
targetEelApi.fs.delete(targetRoot, true).getOrThrow()
|
||||
Files.createDirectory(targetRoot.asNioPath())
|
||||
}
|
||||
}
|
||||
attrs.isRegularFile -> {
|
||||
if (!targetRoot.asNioPath().isRegularFile(LinkOption.NOFOLLOW_LINKS)) {
|
||||
targetEelApi.fs.delete(targetRoot, true).getOrThrow()
|
||||
Files.createFile(targetRoot.asNioPath())
|
||||
}
|
||||
}
|
||||
attrs.isSymbolicLink -> {
|
||||
if (!targetRoot.asNioPath().isSymbolicLink()) {
|
||||
targetEelApi.fs.delete(targetRoot, true).getOrThrow()
|
||||
Files.createSymbolicLink(targetRoot.asNioPath(), Path(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sourceQ: ArrayDeque<EelPath> = ArrayDeque()
|
||||
val targetQ: ArrayDeque<EelPath> = ArrayDeque()
|
||||
sourceQ.add(sourceRoot)
|
||||
@@ -1292,13 +1288,31 @@ object EelPathUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked for each absolute symlink encountered during transfer.
|
||||
* The lambda can do whatever it wants: recreate the symlink, copy contents,
|
||||
* ignore it, call [incrementalWalkingTransfer] again on it, or implement any custom logic.
|
||||
*/
|
||||
fun interface IncrementalWalkingTransferAbsoluteSymlinkHandler {
|
||||
/**
|
||||
* @param sourceSymlink Information about the source symlink
|
||||
* @param targetEntry Directory entry that lives on the path where the source symlink should be. May or may not be a symlink.
|
||||
*/
|
||||
suspend fun handle(sourceSymlink: WalkDirectoryEntry, targetEntry: WalkDirectoryEntry?)
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports transferring directories, files, and symlinks. On POSIX permissions and timestamps are transferred as well if indicated
|
||||
* using [FileTransferAttributesStrategy]. Symlinks are transferred as is, and the target path does not have to be valid.
|
||||
* using [FileTransferAttributesStrategy]. Relative symlinks are transferred as is, and the target path does not have to be valid.
|
||||
* Permissions on symlinks are not transferred as they are ignored by on Unix-like systems.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
private suspend fun incrementalWalkingTransfer(sourceRoot: Path, targetRoot: Path, fileAttributesStrategy: FileTransferAttributesStrategy) {
|
||||
private suspend fun incrementalWalkingTransfer(
|
||||
sourceRoot: Path,
|
||||
targetRoot: Path,
|
||||
fileAttributesStrategy: FileTransferAttributesStrategy,
|
||||
absoluteSymlinkHandler: IncrementalWalkingTransferAbsoluteSymlinkHandler?,
|
||||
) {
|
||||
coroutineScope {
|
||||
val targetRootEel = targetRoot.asEelPath()
|
||||
val remoteDescriptor = targetRootEel.descriptor
|
||||
@@ -1307,8 +1321,32 @@ object EelPathUtils {
|
||||
val localOsFamily = localPathEel.descriptor.osFamily
|
||||
val remoteOsFamily = targetRoot.getEelDescriptor().osFamily
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
directoryOnlySync(localPathEel, targetRootEel, localPathEel.descriptor.toEelApi(), remoteDescriptor.toEelApi(), false, this)
|
||||
val sourceAttrs = sourceRoot.fileAttributesView<BasicFileAttributeView>(LinkOption.NOFOLLOW_LINKS).readAttributes()
|
||||
val targetAttrs = targetRoot.fileAttributesView<BasicFileAttributeView>(LinkOption.NOFOLLOW_LINKS).readAttributes()
|
||||
when {
|
||||
sourceAttrs.isDirectory -> {
|
||||
if (!targetAttrs.isDirectory) {
|
||||
remoteEelApi.fs.delete(targetRootEel, true).getOrThrow()
|
||||
Files.createDirectory(targetRoot)
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
directoryOnlySync(localPathEel, targetRootEel, localPathEel.descriptor.toEelApi(), remoteDescriptor.toEelApi(), false, this)
|
||||
}
|
||||
}
|
||||
sourceAttrs.isRegularFile -> {
|
||||
if (!targetAttrs.isRegularFile) {
|
||||
remoteEelApi.fs.delete(targetRootEel, true).getOrThrow()
|
||||
Files.createFile(targetRoot)
|
||||
}
|
||||
}
|
||||
sourceAttrs.isSymbolicLink -> {
|
||||
// if targetRoot is a directory, it should be deleted to prevent it from being traversed
|
||||
if (targetAttrs.isDirectory) {
|
||||
remoteEelApi.fs.delete(targetRootEel, true).getOrThrow()
|
||||
// a placeholder file is created so that, in the case of an absolute symlink, the user lambda receives the expected target path (where the absolute symlink should be)
|
||||
Files.createFile(targetRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val walkDirectoryOptionsSource = WalkDirectoryOptionsBuilder(localPathEel)
|
||||
@@ -1344,17 +1382,13 @@ object EelPathUtils {
|
||||
try {
|
||||
when (diffOp) {
|
||||
is DiffOperation.Create, is DiffOperation.ReplaceFile -> {
|
||||
if (diffOp is DiffOperation.ReplaceFile) {
|
||||
val ker = diffOp.remoteFile.path.asNioPath()
|
||||
Files.delete(ker)
|
||||
}
|
||||
if (diffOp is DiffOperation.ReplaceFile) Files.delete(diffOp.remoteFile.path.asNioPath())
|
||||
|
||||
val localFile: WalkDirectoryEntry
|
||||
if (diffOp is DiffOperation.Create) {
|
||||
localFile = diffOp.localFile
|
||||
val localFile = if (diffOp is DiffOperation.Create) {
|
||||
diffOp.localFile
|
||||
}
|
||||
else {
|
||||
localFile = (diffOp as DiffOperation.ReplaceFile).localFile
|
||||
(diffOp as DiffOperation.ReplaceFile).localFile
|
||||
}
|
||||
|
||||
val localFileNioPath = localFile.path.asNioPath()
|
||||
@@ -1385,11 +1419,11 @@ object EelPathUtils {
|
||||
is WalkDirectoryEntry.Type.Symlink.Relative -> {
|
||||
var symlinkTarget = (localFile.type as WalkDirectoryEntry.Type.Symlink.Relative).symlinkRelativePath
|
||||
if (localOsFamily != remoteOsFamily) {
|
||||
if (remoteOsFamily.isWindows) {
|
||||
symlinkTarget = symlinkTarget.replace("/", "\\")
|
||||
symlinkTarget = if (remoteOsFamily.isWindows) {
|
||||
symlinkTarget.replace("/", "\\")
|
||||
}
|
||||
else {
|
||||
symlinkTarget = symlinkTarget.replace("\\", "/")
|
||||
symlinkTarget.replace("\\", "/")
|
||||
}
|
||||
}
|
||||
Files.createSymbolicLink(remoteAbsolutePath, Path(symlinkTarget))
|
||||
@@ -1398,7 +1432,7 @@ object EelPathUtils {
|
||||
//setPermissionsAndAttributes(localFile, remoteAbsolutePath, fileAttributesStrategy, false, true, true)
|
||||
}
|
||||
is WalkDirectoryEntry.Type.Symlink.Absolute -> {
|
||||
// TODO: IJPL-201078
|
||||
error("unreachable, absolute symlink should exclusively be handled by the user provided lambda")
|
||||
}
|
||||
is WalkDirectoryEntry.Type.Other -> {
|
||||
// NOTE: other file types not supported
|
||||
@@ -1430,6 +1464,12 @@ object EelPathUtils {
|
||||
setPermissionsAndAttributes(diffOp.localFile, diffOp.remoteFile.path.asNioPath(), remoteEelApi, fileAttributesStrategy, diffOp.updatePermissions, diffOp.updateAttributes, diffOp.updateTimestamps)
|
||||
}
|
||||
}
|
||||
is DiffOperation.AbsoluteSymlink -> {
|
||||
when (absoluteSymlinkHandler) {
|
||||
null -> LOG.info("No absolute symlink handler provided for incremental walking transfer, skipping symlink: ${diffOp.sourceSymlink.path}")
|
||||
else -> absoluteSymlinkHandler.handle(diffOp.sourceSymlink, diffOp.remoteSymlink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
||||
Reference in New Issue
Block a user