Examples
Input: ([('README.md', 'repo'), ('src/main.py', 'main\n'), ('src/util.py', 'add\n')], [('README.md', 'repo'), ('src/main.py', 'main\n'), ('src/util.py', 'add\n')])
Expected Output: []
Explanation: Both snapshots contain the same files with the same contents, so no file differs.
Input: ([('README.md', 'repo'), ('src/main.py', 'v1\n'), ('src/utils/helpers.py', 'helper\n'), ('docs/old.md', 'old\n')], [('README.md', 'repo'), ('src/main.py', 'v2\n'), ('src/utils/helpers.py', 'helper\n'), ('src/utils/new.py', 'new\n'), ('tests/test_main.py', 'ok\n')])
Expected Output: [('docs/old.md', 'REMOVED'), ('src/main.py', 'MODIFIED'), ('src/utils/new.py', 'ADDED'), ('tests/test_main.py', 'ADDED')]
Explanation: One file was removed, one existing file changed contents, and two new files were added.
Input: ([('config', 'port=8080\n'), ('keep.txt', 'same')], [('config/app.yml', 'port: 8080\n'), ('keep.txt', 'same')])
Expected Output: [('config', 'REMOVED'), ('config/app.yml', 'ADDED')]
Explanation: The path `config` changed from a file to a directory containing a file. Treat that as removing the old file and adding the new file path.
Input: ([], [('README.md', ''), ('src/app.py', 'run\n')])
Expected Output: [('README.md', 'ADDED'), ('src/app.py', 'ADDED')]
Explanation: The first snapshot is empty, so every file in the second snapshot is added.
Input: ([('a/b/c.txt', '1'), ('a/d.txt', '2'), ('x.txt', 'same')], [('x.txt', 'same')])
Expected Output: [('a/b/c.txt', 'REMOVED'), ('a/d.txt', 'REMOVED')]
Explanation: The entire `a` subtree exists only in the first snapshot, so all files inside it are reported as removed.
Input: ([('bin/data.bin', b'\x00\x01'), ('same.txt', 'ok')], [('bin/data.bin', b'\x00\x02'), ('same.txt', 'ok')])
Expected Output: [('bin/data.bin', 'MODIFIED')]
Explanation: The binary file contents differ, so that file is modified while the other file is unchanged.