Welcome to treecomp documentation!

    $ treecomp file_trees/one file_trees/two | dunk
                                4 files changed                                 
                                  1 file added                                  
                                 1 file removed                                 
                             +8 ━━━━━━━━━━━━━━━ -8                             

▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/one/c.txt (0 additions, 5 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲  File was removed  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁ file_trees/two/image-two-only.png (0 additions, 0 removals) ▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲  File is binary · 11352 bytes  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁ Added file_trees/two/d.txt (5 additions, 0 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -0,0 +1,5 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1                                       1 a text file in two only             
                                          2 lorem ipsum dolor sit amet, consecte
                                          3 sed do eiusmod tempor incididunt ut 
                                          4 Ut enim ad minim veniam, quis nostru
                                          5 nisi ut aliquip ex ea commodo conseq
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/a.py (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,5 +1,5 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1 def a():                              1 def a():                            
  2 """                               2 """                             
  3 This is a docstring for a in one  3 This is a docstring for a in two
  4 """                               4 """                             
  5 print("a")                        5 print("a")                      
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/b.js (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,6 +1,6 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1                                       1                                     
  2 // This is the documentation          2 // This is the documentation        
  3 // for b in one                       3 // for b in two                     
  4 function b() {                        4 function b() {                      
  5   console.log('b');                   5   console.log('b');                 
  6 }                                     6 }                                   
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/subdir/e.ts (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,2 +1,2 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1 // Defined in folder one              1 // Defined in folder two            
  2 type EType = "E"                      2 type EType = "E"                    
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
                                                             /// dunk 0.4.0a0   

PyPI PyPI - License Documentation Tests Run on Ubuntu Python Versions Tests Run on Macos Python Versions Tests Run on Windows Python Versions Github Repo

treecomp

treecomp example GIF

This GIF doesn't do treecomp justice, check out the examples

Overview

A CLI and Python API to recursively compare directory trees and output a unified diff. Supports ignoring and targeting file and folder patterns with .gitignore-style syntax. Plays well with other tools: pipe output to dunk for pretty diffs and use -f json to output to JSON for use with jq and other tools.

Getting Started

The recommended way to install treecomp is with pipx, though it can also be installed with pip.

pipx install treecomp

Or, if you don’t have/don’t want to install pipx:

pip install treecomp

The unidiff output from this tool is best viewed using dunk, which can also be installed via pipx/pip.

pipx install dunk

Compare two file trees recursively and output unified diffs.

treecomp my_folder_1 my_folder_2 | dunk

It supports ignoring and targeting patterns with .gitignore-style syntax. It also has a strongly-typed Python API.

See examples in the documentation.

Development Status

This project is currently in early-stage development. There may be breaking changes often. While the major version is 0, minor version upgrades will often have breaking changes.

Developing

First ensure that you have pipx installed, if not, install it with pip install pipx.

Then clone the repo and run npm install and pipenv sync. Run pipenv shell to use the virtual environment. Make your changes and then run nox to run formatting, linting, and tests.

Develop documentation by running nox -s docs to start up a dev server.

To run tests only, run nox -s test. You can pass additional arguments to pytest by adding them after --, e.g. nox -s test -- -k test_something.

Author

Created by Nick DeRobertis. MIT License.

Examples

CLI

Ignore files with comma-separated .gitignore-style syntax:

    $ treecomp file_trees/one file_trees/two -i code,*.png | dunk
                                  1 file added                                  
                                 1 file removed                                 
                             +5 ━━━━━━━━━━━━━━━ -5                             

▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/one/c.txt (0 additions, 5 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲  File was removed  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁ Added file_trees/two/d.txt (5 additions, 0 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -0,0 +1,5 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1                                       1 a text file in two only             
                                          2 lorem ipsum dolor sit amet, consecte
                                          3 sed do eiusmod tempor incididunt ut 
                                          4 Ut enim ad minim veniam, quis nostru
                                          5 nisi ut aliquip ex ea commodo conseq
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
                                                             /// dunk 0.4.0a0   

Target files with the same comma-separated .gitignore-style syntax:

    $ treecomp file_trees/one file_trees/two -t *.py,image-two-only.png | dunk
                                2 files changed                                 
                             +1 ━━━━━━━━━━━━━━━ -1                             

▁▁▁▁▁▁▁▁▁ file_trees/two/image-two-only.png (0 additions, 0 removals) ▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲  File is binary · 11352 bytes  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/a.py (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,5 +1,5 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1 def a():                              1 def a():                            
  2 """                               2 """                             
  3 This is a docstring for a in one  3 This is a docstring for a in two
  4 """                               4 """                             
  5 print("a")                        5 print("a")                      
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
                                                             /// dunk 0.4.0a0   

Output to JSON for use with jq and other tools. Here, we select the relative path of all the files that differ:

    $ treecomp file_trees/one file_trees/two -f json | jq '.[].path'
"c.txt"
"image-two-only.png"
"d.txt"
"code/b.js"
"code/a.py"
"code/subdir/e.ts"

We could also use jq to select the files that differ and exist in the left directory (use right to to do same with right directory):

    $ treecomp file_trees/one file_trees/two -f json | jq '.[] | select(.left) | .path'
"c.txt"
"code/b.js"
"code/a.py"
"code/subdir/e.ts"

$ treecomp file_trees/one file_trees/two -f json | jq '.[] | select(.right) | .path'
"image-two-only.png"
"d.txt"
"code/b.js"
"code/a.py"
"code/subdir/e.ts"

There are fancier operations you can do with jq, such as determining which folders have diffs:

    $ treecomp file_trees/one file_trees/two -f json | jq -s '.[] | map(select(.left).path | split("/")) | map(.[:-1]) | map("./" +  (. | join("/"))) | unique'
[
  "./",
  "./code",
  "./code/subdir"
]

You can even use jq to select the diffs you want and then pipe the diffs back to dunk for display, here again selecting only diffs that exist in the left directory:

    $ treecomp file_trees/one file_trees/two -f json | jq -sr '.[] | map(select(.left).diff) | join("\n")' | dunk
                                3 files changed                                 
                                 1 file removed                                 
                             +3 ━━━━━━━━━━━━━━━ -8                             

▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/one/c.txt (0 additions, 5 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲  File was removed  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/a.py (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,5 +1,5 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1 def a():                              1 def a():                            
  2 """                               2 """                             
  3 This is a docstring for a in one  3 This is a docstring for a in two
  4 """                               4 """                             
  5 print("a")                        5 print("a")                      
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/b.js (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,6 +1,6 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1                                       1                                     
  2 // This is the documentation          2 // This is the documentation        
  3 // for b in one                       3 // for b in two                     
  4 function b() {                        4 function b() {                      
  5   console.log('b');                   5   console.log('b');                 
  6 }                                     6 }                                   
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
▁▁▁▁▁▁▁▁▁▁ file_trees/two/code/subdir/e.ts (1 additions, 1 removals) ▁▁▁▁▁▁▁▁▁▁▁
╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ @@ -1,2 +1,2 @@  ╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
  1 // Defined in folder one              1 // Defined in folder two            
  2 type EType = "E"                      2 type EType = "E"                    
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
                                                             /// dunk 0.4.0a0   

Python API

You can also use treecomp as a Python module, with a very similar API:

import treecomp

comp = treecomp.diff_file_trees(
    "file_trees/one", 
    "file_trees/two", 
    ignore=["subdir"], 
    target=["*.py"]
)
print(len(comp), "diffs in total")
for diff in comp:
    if diff.left:
        print("Files with diffs that exist in left:")
        print(diff.path)

API Documentation

Indices