Unison not ignoring paths

I'm trying to ignore .git, .bundle and node_module directories from my sync. I've tried a heap of different combinations trying to get it to work but every time I sync I can see all of those directories syncing over to the remote machine.

Can anyone spot what I'm doing wrong?

~/.unison/default.prf

# Roots of the synchronization
root = /Users/bob/synced
root = ssh://bob@remotebox/synced

# Paths to synchronize
path = hack/testdir

# Some regexps specifying names and paths to ignore
ignore = Name *.log
ignore = Name .DStore
ignore = Name .DS_Store
ignore = Name *.output
ignore = Name *:*
ignore = Path {*/.git/*}
ignore = Path {*/.bundle/*}
ignore = Path {*/.vagrant/*}
ignore = Path {*/.git}
ignore = Path {*/.bundle}
ignore = Path {*/node_modules}

# Window height
height = 37

# propogate file modification times
times = true

# Log actions to the terminal
log = true

auto = true
batch = true

This is syncing from OSX to Windows 10 if that helps.


Here's a link to the Path Specifications and Ignoring Path section of the Unison manual because it's my favorite section when answering questions about Unison. Note that ignore Name = name ignores any path in which the last component matches name. Also

[The character] * matches any sequence of characters not including / (and not beginning with ., when used at the beginning of a name).

So we can ignore paths just like we ignore certain files types. Accounting for the fact that your .git, .bundle and node_module will all have a leading / character, but may be found under a hidden directory that starts with a ., the following lines are what you want:

ignore = Name {*/,.*/}.git
ignore = Name {*/,.*/}.bundle
ignore = Name {*/,.*/}node_module

For anyone who just syncs the entire root directory, there is an unfortunate corner-case that this doesn't cover: when these files are at the top level, right in the root directory you are syncing, in which case there is no leading / character. This issue is avoided in the above profile because only the directory testdir is being synced with the line path = hack/testdir. Anyways, for someone who syncs the whole root directory you could add something like .*,* to the globbing patterns, but these patterns will match any file with a name like foo.git too. So you can either resolve to not have any directories/files named .git, etc, in the top level of your root directory, or if you're okay matching things like foo.git too, then you can use the lines:

ignore = Name {.*,*,*/,.*/}.git
ignore = Name {.*,*,*/,.*/}.bundle
ignore = Name {.*,*,*/,.*/}node_module