Skip to content

Code smell: Folder.CopyTo() doesn't preserve file attributes or timestamps #1603

@thomhurst

Description

@thomhurst

Location

  • src/ModularPipelines/FileSystem/Folder.cs (lines 108-129)

Problem

The CopyTo method copies files but doesn't preserve metadata:

public Folder CopyTo(string targetPath)
{
    Directory.CreateDirectory(targetPath);

    foreach (var dirPath in Directory.EnumerateDirectories(this, "*", SearchOption.AllDirectories))
    {
        var relativePath = System.IO.Path.GetRelativePath(this, dirPath);
        var newPath = System.IO.Path.Combine(targetPath, relativePath);
        Directory.CreateDirectory(newPath);
    }

    foreach (var filePath in Directory.EnumerateFiles(this, "*", SearchOption.AllDirectories))
    {
        var relativePath = System.IO.Path.GetRelativePath(this, filePath);
        var newPath = System.IO.Path.Combine(targetPath, relativePath);
        System.IO.File.Copy(filePath, newPath, true);  // Doesn't preserve timestamps!
    }

    ModuleLogger.Current.LogInformation("Copying Folder: {Source} > {Destination}", this, targetPath);

    return new Folder(targetPath);
}

Issues:

  1. File timestamps (creation, modification, access times) are not preserved
  2. File attributes (read-only, hidden, etc.) may not be preserved
  3. Directory timestamps are not preserved
  4. No option to control this behavior

Impact

  • Build tools that rely on timestamps may behave incorrectly
  • Incremental builds may not work properly
  • File metadata is lost during copy operations

Suggested Fix

Add an option to preserve metadata:

public Folder CopyTo(string targetPath, bool preserveTimestamps = true, bool preserveAttributes = true)
{
    Directory.CreateDirectory(targetPath);

    foreach (var dirPath in Directory.EnumerateDirectories(this, "*", SearchOption.AllDirectories))
    {
        var relativePath = System.IO.Path.GetRelativePath(this, dirPath);
        var newPath = System.IO.Path.Combine(targetPath, relativePath);
        Directory.CreateDirectory(newPath);
        
        if (preserveTimestamps)
        {
            var sourceDir = new DirectoryInfo(dirPath);
            var destDir = new DirectoryInfo(newPath);
            destDir.CreationTimeUtc = sourceDir.CreationTimeUtc;
            destDir.LastWriteTimeUtc = sourceDir.LastWriteTimeUtc;
            destDir.LastAccessTimeUtc = sourceDir.LastAccessTimeUtc;
        }
        
        if (preserveAttributes)
        {
            new DirectoryInfo(newPath).Attributes = new DirectoryInfo(dirPath).Attributes;
        }
    }

    foreach (var filePath in Directory.EnumerateFiles(this, "*", SearchOption.AllDirectories))
    {
        var relativePath = System.IO.Path.GetRelativePath(this, filePath);
        var newPath = System.IO.Path.Combine(targetPath, relativePath);
        System.IO.File.Copy(filePath, newPath, true);
        
        if (preserveTimestamps)
        {
            var sourceFile = new FileInfo(filePath);
            var destFile = new FileInfo(newPath);
            destFile.CreationTimeUtc = sourceFile.CreationTimeUtc;
            destFile.LastWriteTimeUtc = sourceFile.LastWriteTimeUtc;
            destFile.LastAccessTimeUtc = sourceFile.LastAccessTimeUtc;
        }
        
        if (preserveAttributes)
        {
            new FileInfo(newPath).Attributes = new FileInfo(filePath).Attributes;
        }
    }

    ModuleLogger.Current.LogInformation("Copying Folder: {Source} > {Destination}", this, targetPath);

    return new Folder(targetPath);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions