Building a Patch From Two Directories

The task of comparing two directories comes up fairly often, and the standard Unix diff+patch handles it reasonably well, but in my case I specifically needed to produce an archive of the changes. I could have written a C++/C# program in a few minutes, but I set myself the goal of using a proper Unix-native solution.

At first I spent a good while poking around with diff, KDiff3, and similar programs, but the result was far from what I needed, so I decided to write a .sh script instead. I’m not much of a Unix/Linux guru, so every step came with a long round of googling. 🙂

File listing

The first step was getting the file lists. Variations on the ls -R command give you the right files, but in an inconvenient form that needs further processing. By the time I got to ls -lR|tr -s ‘ ‘|cut -d ‘ ‘ -f 9, I decided I’d had enough, gave up on ls, and used find instead. The downside of that command is that it prepends a relative path to every file. You can avoid that by cd-ing into the folder first and running find ., but then for some unclear reason symlinks disappear from the list, and using the -type fl parameter doesn’t help. The proper fix turned out to be trimming the “extra” path with sed.

find $2 | sed -e "s:^$2/::" > files.txt

Iterating and comparing

I decided to iterate over every file in the second folder and check whether it exists in the first. A fairly simple loop calling cmp:

for f in $(cat files.txt)
do
    if [ -f "$2/$f" ]
    then
        if ! cmp -s "$1/$f" "$2/$f"; then
                echo $f >> toadd.txt
        fi
    fi
done

The [ -f “$2/$f” ] check is there to filter out directories. If the file doesn’t exist at path “$1/$f”, it also ends up in the list, which suited me fine.

Archive

The resulting toadd.txt list gets fed to the tar command. To get correct paths inside the archive, you need an extra cd:

cd $2
tar -czvf ../result.tar.gz --files-from ../toadd.txt
cd ..

Epilogue

After an hour spent on this, I now have a utility that lets me compare, say, two firmware images for some device, or different versions of repositories, and get a tar.gz (or any other) archive of the differences as output. Of course, using diff and patch would give a much smaller result, but what you get this way is far more transparent and easier to use. One notable shortcoming of this approach – empty directories don’t make it into the archive.
Full script file: http://runserver.net/temp/ddiff.sh

Originally published 2010-10-19.