Getting rsync to delete old non-empty directories containing excluded files

For some older projects we’re still deploying code with rsync, its not perfect but it works. Temporary files are excluded using --exclude-from=exclude.txt, this is great until the parent folder of an excluded file needs to be deleted.

For example, say your file structure looks like this:
Rsync delete file structure

And you remove the data/cache directory completely:

$ rm -rf data/cache
$ rsync --exclude="*.tmp" --exclude="data/logs/*.log"

You’re going to get the error “cannot delete non-empty directory: data/cache” because data/cache/config.tmp has not been removed by rsync.

Unfortunately --delete-excluded won’t work because you don’t want to delete the log files and other .tmp files created on the destination.

Fortunately rsync has filters (which include/exclude are shorthand for anyway), with a mode called “perishable”. Perishable excludes behave exactly as required, files are excluded from sync, and aren’t deleted on the destination unless they are in a directory that no longer exists.

The syntax for excluding files becomes:
$ rsync --filter="-,p *.tmp" --filter="-,p data/logs/"

The “-” signifies the filter is to exclude matches. The “p” makes the exclude perishable.

If, like us, you were using --exclude-from for the patterns, you can with this syntax:
$ rsync --filter="merge,p- /home/ideal/scripts/push/excludes/lenta.txt"

This information is all available in the rsync docs under “FILTER RULES”, it just took me a while to figure out the right syntax. Hopefully these snippets help someone out.