From 2aed0a74820923d86b171baf407c131414f56093 Mon Sep 17 00:00:00 2001 From: Graciliano Monteiro Passos Date: Tue, 10 Dec 2024 18:47:38 -0300 Subject: [PATCH 1/9] copyPath/copyPathSync: expose parameter `followLinks` --- pkgs/io/lib/src/copy_path.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkgs/io/lib/src/copy_path.dart b/pkgs/io/lib/src/copy_path.dart index 3a999b610..ce1fd5a7c 100644 --- a/pkgs/io/lib/src/copy_path.dart +++ b/pkgs/io/lib/src/copy_path.dart @@ -23,14 +23,17 @@ bool _doNothing(String from, String to) { /// * Existing files are over-written, if any. /// * If [to] is within [from], throws [ArgumentError] (an infinite operation). /// * If [from] and [to] are canonically the same, no operation occurs. +/// * If [followLinks] is `true`, then working links are reported as +/// directories or files, and links to directories are recursed into. /// /// Returns a future that completes when complete. -Future copyPath(String from, String to) async { +Future copyPath(String from, String to, {bool followLinks = true}) async { if (_doNothing(from, to)) { return; } await Directory(to).create(recursive: true); - await for (final file in Directory(from).list(recursive: true)) { + await for (final file + in Directory(from).list(recursive: true, followLinks: followLinks)) { final copyTo = p.join(to, p.relative(file.path, from: from)); if (file is Directory) { await Directory(copyTo).create(recursive: true); @@ -49,14 +52,17 @@ Future copyPath(String from, String to) async { /// * Existing files are over-written, if any. /// * If [to] is within [from], throws [ArgumentError] (an infinite operation). /// * If [from] and [to] are canonically the same, no operation occurs. +/// * If [followLinks] is `true`, then working links are reported as +/// directories or files, and links to directories are recursed into. /// /// This action is performed synchronously (blocking I/O). -void copyPathSync(String from, String to) { +void copyPathSync(String from, String to, {bool followLinks = true}) { if (_doNothing(from, to)) { return; } Directory(to).createSync(recursive: true); - for (final file in Directory(from).listSync(recursive: true)) { + for (final file + in Directory(from).listSync(recursive: true, followLinks: followLinks)) { final copyTo = p.join(to, p.relative(file.path, from: from)); if (file is Directory) { Directory(copyTo).createSync(recursive: true); From 3946b3a7507291834051a4bb5f050e821a44e300 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 14 Jan 2025 00:27:09 +0000 Subject: [PATCH 2/9] Add changelog entry --- pkgs/io/CHANGELOG.md | 4 ++++ pkgs/io/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/io/CHANGELOG.md b/pkgs/io/CHANGELOG.md index e0631fa95..99c7d027f 100644 --- a/pkgs/io/CHANGELOG.md +++ b/pkgs/io/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Add a `followLinks` argument to `copyPath` and `copyPathSync`. + ## 1.0.5 * Require Dart 3.4. diff --git a/pkgs/io/pubspec.yaml b/pkgs/io/pubspec.yaml index 7e00d993d..46b76b362 100644 --- a/pkgs/io/pubspec.yaml +++ b/pkgs/io/pubspec.yaml @@ -2,7 +2,7 @@ name: io description: >- Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values. -version: 1.0.5 +version: 1.1.0-wip repository: https://github.com/dart-lang/tools/tree/main/pkgs/io environment: From 305e94db46de3623d1796676dabe524cf0c0280c Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 14 Jan 2025 23:04:29 +0000 Subject: [PATCH 3/9] Add -wip suffix in changelog to match pubspec --- pkgs/io/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/io/CHANGELOG.md b/pkgs/io/CHANGELOG.md index 99c7d027f..f5e99881c 100644 --- a/pkgs/io/CHANGELOG.md +++ b/pkgs/io/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.1.0 +## 1.1.0-wip * Add a `followLinks` argument to `copyPath` and `copyPathSync`. From 21a9a741ce155a9a731f8679013b0431e952666d Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 16 Jan 2025 01:19:49 +0000 Subject: [PATCH 4/9] Add tests --- pkgs/io/test/copy_path_test.dart | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index 0c72a0b4d..f34519956 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -5,6 +5,8 @@ @TestOn('vm') library; +import 'dart:io'; + import 'package:io/io.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -33,6 +35,76 @@ void main() { throwsArgumentError, ); }); + + group('links', () { + const linkTarget = 'link_target'; + const linkSource = 'link_source'; + const linkContent = 'link_content.txt'; + late String targetPath; + setUp(() async { + await _create(); + await d + .dir(linkTarget, [d.file(linkContent, 'original content')]).create(); + targetPath = p.join(d.sandbox, linkTarget); + await Link(p.join(d.sandbox, _parentDir, linkSource)).create(targetPath); + }); + + test('are shallow copied with deepCopyLinks: false in copyPath', () async { + await copyPath( + followLinks: false, + p.join(d.sandbox, _parentDir), + p.join(d.sandbox, _copyDir)); + + final expectedLink = Link(p.join(d.sandbox, _copyDir, linkSource)); + expect(await expectedLink.exists(), isTrue); + expect(await expectedLink.target(), targetPath); + }); + + test('are shallow copied with deepCopyLinks: false in copyPathAsync', + () async { + copyPathSync( + followLinks: false, + p.join(d.sandbox, _parentDir), + p.join(d.sandbox, _copyDir)); + + final expectedLink = Link(p.join(d.sandbox, _copyDir, linkSource)); + expect(await expectedLink.exists(), isTrue); + expect(await expectedLink.target(), targetPath); + }); + + test('are deep copied by default in copyPath', () async { + await copyPath( + p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir)); + + final expectedDir = Directory(p.join(d.sandbox, _copyDir, linkSource)); + final expectedFile = + File(p.join(d.sandbox, _copyDir, linkSource, linkContent)); + expect(await expectedDir.exists(), isTrue); + expect(await expectedFile.exists(), isTrue); + + await expectedFile.writeAsString('new content'); + final originalFile = + File(p.join(d.sandbox, _parentDir, linkSource, linkContent)); + expect(await originalFile.readAsString(), 'original content', + reason: 'The file behind the link should not change'); + }); + + test('are deep copied by default in copyPathAsync', () async { + copyPathSync(p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir)); + + final expectedDir = Directory(p.join(d.sandbox, _copyDir, linkSource)); + final expectedFile = + File(p.join(d.sandbox, _copyDir, linkSource, linkContent)); + expect(await expectedDir.exists(), isTrue); + expect(await expectedFile.exists(), isTrue); + + await expectedFile.writeAsString('new content'); + final originalFile = + File(p.join(d.sandbox, _parentDir, linkSource, linkContent)); + expect(await originalFile.readAsString(), 'original content', + reason: 'The file behind the link should not change'); + }); + }); } const _parentDir = 'parent'; From a198283c52f77a251ee8dae361b2c1ba5c3e5b79 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 16 Jan 2025 01:25:07 +0000 Subject: [PATCH 5/9] Update argument name and documentation --- pkgs/io/CHANGELOG.md | 2 +- pkgs/io/lib/src/copy_path.dart | 25 ++++++++++++++----------- pkgs/io/test/copy_path_test.dart | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pkgs/io/CHANGELOG.md b/pkgs/io/CHANGELOG.md index f5e99881c..8c0057f2b 100644 --- a/pkgs/io/CHANGELOG.md +++ b/pkgs/io/CHANGELOG.md @@ -1,6 +1,6 @@ ## 1.1.0-wip -* Add a `followLinks` argument to `copyPath` and `copyPathSync`. +* Add a `deepCopyLinks` argument to `copyPath` and `copyPathSync`. ## 1.0.5 diff --git a/pkgs/io/lib/src/copy_path.dart b/pkgs/io/lib/src/copy_path.dart index ce1fd5a7c..8a1c3ca46 100644 --- a/pkgs/io/lib/src/copy_path.dart +++ b/pkgs/io/lib/src/copy_path.dart @@ -19,21 +19,23 @@ bool _doNothing(String from, String to) { /// Copies all of the files in the [from] directory to [to]. /// /// This is similar to `cp -R `: -/// * Symlinks are supported. /// * Existing files are over-written, if any. /// * If [to] is within [from], throws [ArgumentError] (an infinite operation). /// * If [from] and [to] are canonically the same, no operation occurs. -/// * If [followLinks] is `true`, then working links are reported as -/// directories or files, and links to directories are recursed into. +/// * If [deepCopyLinks] is `true` (the default) then links are followed and +/// the content of linked directories and files are copied entirely. If +/// `false` then new [Link] file system entities are created linking to the +/// same target the links under [from]. /// /// Returns a future that completes when complete. -Future copyPath(String from, String to, {bool followLinks = true}) async { +Future copyPath(String from, String to, + {bool deepCopyLinks = true}) async { if (_doNothing(from, to)) { return; } await Directory(to).create(recursive: true); await for (final file - in Directory(from).list(recursive: true, followLinks: followLinks)) { + in Directory(from).list(recursive: true, followLinks: deepCopyLinks)) { final copyTo = p.join(to, p.relative(file.path, from: from)); if (file is Directory) { await Directory(copyTo).create(recursive: true); @@ -48,21 +50,22 @@ Future copyPath(String from, String to, {bool followLinks = true}) async { /// Copies all of the files in the [from] directory to [to]. /// /// This is similar to `cp -R `: -/// * Symlinks are supported. /// * Existing files are over-written, if any. /// * If [to] is within [from], throws [ArgumentError] (an infinite operation). /// * If [from] and [to] are canonically the same, no operation occurs. -/// * If [followLinks] is `true`, then working links are reported as -/// directories or files, and links to directories are recursed into. +/// * If [deepCopyLinks] is `true` (the default) then links are followed and +/// the content of linked directories and files are copied entirely. If +/// `false` then new [Link] file system entities are created linking to the +/// same target the links under [from]. /// /// This action is performed synchronously (blocking I/O). -void copyPathSync(String from, String to, {bool followLinks = true}) { +void copyPathSync(String from, String to, {bool deepCopyLinks = true}) { if (_doNothing(from, to)) { return; } Directory(to).createSync(recursive: true); - for (final file - in Directory(from).listSync(recursive: true, followLinks: followLinks)) { + for (final file in Directory(from) + .listSync(recursive: true, followLinks: deepCopyLinks)) { final copyTo = p.join(to, p.relative(file.path, from: from)); if (file is Directory) { Directory(copyTo).createSync(recursive: true); diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index f34519956..af3cb36a0 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -51,7 +51,7 @@ void main() { test('are shallow copied with deepCopyLinks: false in copyPath', () async { await copyPath( - followLinks: false, + deepCopyLinks: false, p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir)); @@ -63,7 +63,7 @@ void main() { test('are shallow copied with deepCopyLinks: false in copyPathAsync', () async { copyPathSync( - followLinks: false, + deepCopyLinks: false, p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir)); From 16ce369ddc74241001f1225b66d295acfee2513b Mon Sep 17 00:00:00 2001 From: Graciliano Monteiro Passos Date: Thu, 16 Jan 2025 14:28:48 -0300 Subject: [PATCH 6/9] Update pkgs/io/test/copy_path_test.dart Co-authored-by: Jacob MacDonald --- pkgs/io/test/copy_path_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index af3cb36a0..1eeea7dac 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -60,7 +60,7 @@ void main() { expect(await expectedLink.target(), targetPath); }); - test('are shallow copied with deepCopyLinks: false in copyPathAsync', + test('are shallow copied with deepCopyLinks: false in copyPathSync', () async { copyPathSync( deepCopyLinks: false, From 0d524384befd73e116bac70fd4cb5ccc7263c427 Mon Sep 17 00:00:00 2001 From: Graciliano Monteiro Passos Date: Thu, 16 Jan 2025 14:29:01 -0300 Subject: [PATCH 7/9] Update pkgs/io/test/copy_path_test.dart Co-authored-by: Jacob MacDonald --- pkgs/io/test/copy_path_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index 1eeea7dac..d3f9688f3 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -89,7 +89,7 @@ void main() { reason: 'The file behind the link should not change'); }); - test('are deep copied by default in copyPathAsync', () async { + test('are deep copied by default in copyPathSync', () async { copyPathSync(p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir)); final expectedDir = Directory(p.join(d.sandbox, _copyDir, linkSource)); From 99b6b19573cb01f3f809239e87d5f1ae8eb16816 Mon Sep 17 00:00:00 2001 From: Graciliano Monteiro Passos Date: Thu, 16 Jan 2025 14:33:45 -0300 Subject: [PATCH 8/9] Update copy_path_test.dart check content of copied linked file --- pkgs/io/test/copy_path_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index d3f9688f3..79739aa65 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -82,6 +82,9 @@ void main() { expect(await expectedDir.exists(), isTrue); expect(await expectedFile.exists(), isTrue); + expect(await expectedFile.readAsString(), 'original content', + reason: 'The file behind the link was copied with invalid content'); + await expectedFile.writeAsString('new content'); final originalFile = File(p.join(d.sandbox, _parentDir, linkSource, linkContent)); @@ -98,6 +101,9 @@ void main() { expect(await expectedDir.exists(), isTrue); expect(await expectedFile.exists(), isTrue); + expect(await expectedFile.readAsString(), 'original content', + reason: 'The file behind the link was copied with invalid content'); + await expectedFile.writeAsString('new content'); final originalFile = File(p.join(d.sandbox, _parentDir, linkSource, linkContent)); From b64cbf38ff0b7a2d1477dd3872bd7ed9ffdc215e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 16 Jan 2025 21:20:29 -0300 Subject: [PATCH 9/9] dart format --- pkgs/io/test/copy_path_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart index 79739aa65..df10395b6 100644 --- a/pkgs/io/test/copy_path_test.dart +++ b/pkgs/io/test/copy_path_test.dart @@ -84,7 +84,7 @@ void main() { expect(await expectedFile.readAsString(), 'original content', reason: 'The file behind the link was copied with invalid content'); - + await expectedFile.writeAsString('new content'); final originalFile = File(p.join(d.sandbox, _parentDir, linkSource, linkContent)); @@ -103,7 +103,7 @@ void main() { expect(await expectedFile.readAsString(), 'original content', reason: 'The file behind the link was copied with invalid content'); - + await expectedFile.writeAsString('new content'); final originalFile = File(p.join(d.sandbox, _parentDir, linkSource, linkContent));