Update copyDirectory to allow links to not be followed (#144040)

In other words, copy links within a directory as links rather than copying them as files/directories. 

Fixes https://github.com/flutter/flutter/issues/144032.
This commit is contained in:
Victoria Ashworth 2024-02-26 10:07:22 -06:00 committed by GitHub
parent d05eaf4a98
commit 45c8881eb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 109 additions and 1 deletions

View file

@ -109,12 +109,19 @@ String getDisplayPath(String fullPath, FileSystem fileSystem) {
///
/// Skips files if [shouldCopyFile] returns `false`.
/// Does not recurse over directories if [shouldCopyDirectory] returns `false`.
///
/// If [followLinks] is false, then any symbolic links found are reported as
/// [Link] objects, rather than as directories or files, and are not recursed into.
///
/// If [followLinks] is true, then working links are reported as directories or
/// files, depending on what they point to.
void copyDirectory(
Directory srcDir,
Directory destDir, {
bool Function(File srcFile, File destFile)? shouldCopyFile,
bool Function(Directory)? shouldCopyDirectory,
void Function(File srcFile, File destFile)? onFileCopied,
bool followLinks = true,
}) {
if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
@ -124,7 +131,7 @@ void copyDirectory(
destDir.createSync(recursive: true);
}
for (final FileSystemEntity entity in srcDir.listSync()) {
for (final FileSystemEntity entity in srcDir.listSync(followLinks: followLinks)) {
final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is Link) {
final Link newLink = destDir.fileSystem.link(newPath);
@ -145,6 +152,7 @@ void copyDirectory(
destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied,
followLinks: followLinks,
);
} else {
throw Exception('${entity.path} is neither File nor Directory, was ${entity.runtimeType}');

View file

@ -88,6 +88,106 @@ void main() {
expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3);
});
testWithoutContext('test directory copy with followLinks: true', () async {
final Signals signals = Signals.test();
final LocalFileSystem fileSystem = LocalFileSystem.test(
signals: signals,
);
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_copy_directory.');
try {
final String sourcePath = io.Platform.isWindows ? r'some\origin' : 'some/origin';
final Directory sourceDirectory = tempDir.childDirectory(sourcePath)..createSync(recursive: true);
final File sourceFile1 = sourceDirectory.childFile('some_file.txt')..writeAsStringSync('file 1');
sourceDirectory.childLink('absolute_linked.txt').createSync(sourceFile1.absolute.path);
final DateTime writeTime = sourceFile1.lastModifiedSync();
final Directory sourceSubDirectory = sourceDirectory.childDirectory('dir1').childDirectory('dir2')..createSync(recursive: true);
sourceSubDirectory.childFile('another_file.txt').writeAsStringSync('file 2');
final String subdirectorySourcePath = io.Platform.isWindows ? r'dir1\dir2' : 'dir1/dir2';
sourceDirectory.childLink('relative_linked_sub_dir').createSync(subdirectorySourcePath);
sourceDirectory.childDirectory('empty_directory').createSync(recursive: true);
final String targetPath = io.Platform.isWindows ? r'some\non-existent\target' : 'some/non-existent/target';
final Directory targetDirectory = tempDir.childDirectory(targetPath);
copyDirectory(sourceDirectory, targetDirectory);
expect(targetDirectory.existsSync(), true);
expect(targetDirectory.childFile('some_file.txt').existsSync(), true);
expect(targetDirectory.childFile('some_file.txt').readAsStringSync(), 'file 1');
expect(targetDirectory.childFile('absolute_linked.txt').readAsStringSync(), 'file 1');
expect(targetDirectory.childLink('absolute_linked.txt').existsSync(), false);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').existsSync(), true);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').childFile('another_file.txt').existsSync(), true);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').childFile('another_file.txt').readAsStringSync(), 'file 2');
expect(targetDirectory.childDirectory('relative_linked_sub_dir').existsSync(), true);
expect(targetDirectory.childLink('relative_linked_sub_dir').existsSync(), false);
expect(targetDirectory.childDirectory('relative_linked_sub_dir').childFile('another_file.txt').existsSync(), true);
expect(targetDirectory.childDirectory('relative_linked_sub_dir').childFile('another_file.txt').readAsStringSync(), 'file 2');
expect(targetDirectory.childDirectory('empty_directory').existsSync(), true);
// Assert that the copy operation hasn't modified the original file in some way.
expect(sourceDirectory.childFile('some_file.txt').lastModifiedSync(), writeTime);
// There's still 5 things in the original directory as there were initially.
expect(sourceDirectory.listSync().length, 5);
} finally {
tryToDelete(tempDir);
}
});
testWithoutContext('test directory copy with followLinks: false', () async {
final Signals signals = Signals.test();
final LocalFileSystem fileSystem = LocalFileSystem.test(
signals: signals,
);
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_copy_directory.');
try {
final String sourcePath = io.Platform.isWindows ? r'some\origin' : 'some/origin';
final Directory sourceDirectory = tempDir.childDirectory(sourcePath)..createSync(recursive: true);
final File sourceFile1 = sourceDirectory.childFile('some_file.txt')..writeAsStringSync('file 1');
sourceDirectory.childLink('absolute_linked.txt').createSync(sourceFile1.absolute.path);
final DateTime writeTime = sourceFile1.lastModifiedSync();
final Directory sourceSubDirectory = sourceDirectory.childDirectory('dir1').childDirectory('dir2')..createSync(recursive: true);
sourceSubDirectory.childFile('another_file.txt').writeAsStringSync('file 2');
final String subdirectorySourcePath = io.Platform.isWindows ? r'dir1\dir2' : 'dir1/dir2';
sourceDirectory.childLink('relative_linked_sub_dir').createSync(subdirectorySourcePath);
sourceDirectory.childDirectory('empty_directory').createSync(recursive: true);
final String targetPath = io.Platform.isWindows ? r'some\non-existent\target' : 'some/non-existent/target';
final Directory targetDirectory = tempDir.childDirectory(targetPath);
copyDirectory(sourceDirectory, targetDirectory, followLinks: false);
expect(targetDirectory.existsSync(), true);
expect(targetDirectory.childFile('some_file.txt').existsSync(), true);
expect(targetDirectory.childFile('some_file.txt').readAsStringSync(), 'file 1');
expect(targetDirectory.childFile('absolute_linked.txt').readAsStringSync(), 'file 1');
expect(targetDirectory.childLink('absolute_linked.txt').existsSync(), true);
expect(
targetDirectory.childLink('absolute_linked.txt').targetSync(),
sourceFile1.absolute.path,
);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').existsSync(), true);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').childFile('another_file.txt').existsSync(), true);
expect(targetDirectory.childDirectory('dir1').childDirectory('dir2').childFile('another_file.txt').readAsStringSync(), 'file 2');
expect(targetDirectory.childDirectory('relative_linked_sub_dir').existsSync(), true);
expect(targetDirectory.childLink('relative_linked_sub_dir').existsSync(), true);
expect(
targetDirectory.childLink('relative_linked_sub_dir').targetSync(),
subdirectorySourcePath,
);
expect(targetDirectory.childDirectory('relative_linked_sub_dir').childFile('another_file.txt').existsSync(), true);
expect(targetDirectory.childDirectory('relative_linked_sub_dir').childFile('another_file.txt').readAsStringSync(), 'file 2');
expect(targetDirectory.childDirectory('empty_directory').existsSync(), true);
// Assert that the copy operation hasn't modified the original file in some way.
expect(sourceDirectory.childFile('some_file.txt').lastModifiedSync(), writeTime);
// There's still 5 things in the original directory as there were initially.
expect(sourceDirectory.listSync().length, 5);
} finally {
tryToDelete(tempDir);
}
});
testWithoutContext('Skip files if shouldCopyFile returns false', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final Directory origin = fileSystem.directory('/origin');