Sync only changed files between two folders in Python

This code compares two folders and copies only the new or modified files from source to destination, skipping unchanged ones by comparing SHA-256 hashes.

Easy Python 3.9+ Aug 9, 2026 Files & data 16 views 0 copies

Python code

36 lines
Python 3.9+
import hashlib
from pathlib import Path
import shutil

def file_hash(path: Path, chunk_size: int = 8192) -> str:
    hasher = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(chunk_size), b""):
            hasher.update(chunk)
    return hasher.hexdigest()

def sync_files(src: str, dst: str) -> list[str]:
    src_path = Path(src)
    dst_path = Path(dst)
    dst_path.mkdir(parents=True, exist_ok=True)
    synced = []

    for src_file in src_path.rglob("*"):
        if src_file.is_file():
            rel_path = src_file.relative_to(src_path)
            dst_file = dst_path / rel_path
            if not dst_file.exists() or file_hash(src_file) != file_hash(dst_file):
                dst_file.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src_file, dst_file)
                synced.append(str(rel_path))

    return synced

if __name__ == "__main__":
    changed = sync_files("source_folder", "backup_folder")
    if changed:
        print("Synced files:")
        for path in changed:
            print(f"  {path}")
    else:
        print("No changes detected — everything is up to date.")

Output

stdout
Synced files:
  data.txt
  reports/q1.csv

How it works

The script walks the entire source tree with Path.rglob("*"), so it captures files in subfolders too. For each file, it computes a SHA-256 hash in chunks, which avoids loading large files into memory all at once. It only copies when the destination file is missing or its hash differs, so unchanged files are skipped. Directories under the destination are created on demand with mkdir(parents=True, exist_ok=True). Using shutil.copy2 preserves metadata like timestamps, which is useful for backups.

Common mistakes

  • Using `os.path.exists` on directories and forgetting that only files should be compared — always check `is_file()`.
  • Hashing large files without chunking can exhaust memory; use a loop that reads fixed-size blocks.
  • Forgetting to create destination subdirectories before copying, which raises `FileNotFoundError`.
  • Comparing file modification times instead of content hashes may miss changes when timestamps are preserved but content differs.

Variations

  1. Use `filecmp.cmp(src_file, dst_file, shallow=False)` to compare content directly without computing hashes yourself.
  2. For very large trees, use `os.walk` combined with a set of relative paths to speed up the check.

Real-world use cases

  • Incremental backups that copy only new or changed files to a backup location, saving time and storage.
  • Deploying website assets or configs to a server by syncing only files that changed between the local and remote directory.
  • Mirroring a dataset folder to a shared network drive, skipping unchanged files to minimize transfer time.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Files & data

Related tutorials and quizzes for this topic.