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`. /// Skips files if [shouldCopyFile] returns `false`.
/// Does not recurse over directories if [shouldCopyDirectory] 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( void copyDirectory(
Directory srcDir, Directory srcDir,
Directory destDir, { Directory destDir, {
bool Function(File srcFile, File destFile)? shouldCopyFile, bool Function(File srcFile, File destFile)? shouldCopyFile,
bool Function(Directory)? shouldCopyDirectory, bool Function(Directory)? shouldCopyDirectory,
void Function(File srcFile, File destFile)? onFileCopied, void Function(File srcFile, File destFile)? onFileCopied,
bool followLinks = true,
}) { }) {
if (!srcDir.existsSync()) { if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy'); throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
@ -124,7 +131,7 @@ void copyDirectory(
destDir.createSync(recursive: true); 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); final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is Link) { if (entity is Link) {
final Link newLink = destDir.fileSystem.link(newPath); final Link newLink = destDir.fileSystem.link(newPath);
@ -145,6 +152,7 @@ void copyDirectory(
destDir.fileSystem.directory(newPath), destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile, shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied, onFileCopied: onFileCopied,
followLinks: followLinks,
); );
} else { } else {
throw Exception('${entity.path} is neither File nor Directory, was ${entity.runtimeType}'); 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); 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', () { testWithoutContext('Skip files if shouldCopyFile returns false', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final Directory origin = fileSystem.directory('/origin'); final Directory origin = fileSystem.directory('/origin');