Skip to content

Instantly share code, notes, and snippets.

@peterdemartini
Last active April 28, 2024 02:42
Show Gist options
  • Save peterdemartini/4c918635208943e7a042ff5ffa789fc1 to your computer and use it in GitHub Desktop.
Save peterdemartini/4c918635208943e7a042ff5ffa789fc1 to your computer and use it in GitHub Desktop.
Exclude node_modules in timemachine
find `pwd` -type d -maxdepth 3 -name 'node_modules' | xargs -n 1 tmutil addexclusion
@poma
Copy link

poma commented Dec 14, 2020

It only excludes node_modules if a file named package.json is present in the same dir. Might not be as important for distinctly named node_modules dir but it's more relevant for example for rust's target dir which can have the same name as something useful outside of rust projects

@tomitrescak
Copy link

tomitrescak commented Mar 9, 2022

@poma found the way which is LEAST obtrusive on all system resources. I extended this script to parse everything in the .gitignore. I am a NOOB in bash scripting and I'm sure it can be made simpler and I'd love to see that. Here it goes:

I have also added handling of .nosync for iCloud Drive

TRIMMED=""

trim() {
  local s2 s="$*"
  until s2="${s#[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  until s2="${s%[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  #echo "====${s}===="
  TRIMMED="$s"
}

tmutil_exclude() {
    # todo: recurse to parent dirs to support commands that execute in project subdirs
    DIR="$1"
    DEP_FILE=$2

    if [ -d "$DIR" ] && [ -f "$DEP_FILE" ] && ! tmutil isexcluded "$DIR" | grep -q '\[Excluded\]'; then
        tmutil addexclusion "$DIR"
        echo "tmutil: ${DIR} has been excluded from Time Machine backups"
    fi

    # Handles iCloud Drive
    CLOUD_FILE="${DIR}/.nosync"

    if [ -d "$DIR" ] && [ ! -f "${CLOUD_FILE}" ]; then
        touch "${CLOUD_FILE}"
        echo ".nosync added at ${CLOUD_FILE}"
    fi
}

__npm_wrapper () {
    # command pnpm "$@"
    EXIT_CODE=$?

    input=".gitignore"
    while IFS= read -r line
    do
        if [ ${line:0:1} = "/" ]; then
            trim ".${line}"
            tmutil_exclude "${TRIMMED}" "package.json"
        fi
    done < "$input"

    return $EXIT_CODE
}

alias pnpm=__npm_wrapper

@dj1020
Copy link

dj1020 commented Jun 6, 2023

Trying to use fd to replace find

fd -t d -d 5 -a --no-ignore --prune node_modules | xargs -I {} tmutil addexclusion {}

@n8henrie
Copy link

n8henrie commented Dec 14, 2023

This thread shows up fairly high in my searches for excluding python virtualenvs from timemachine.

Just wanted to point out for folks looking to dig a little deeper that there is some good info out there on the xattr that's being set on the item leading to exclusion, including faster ways to find these files (with mdfind -- though this will skip files that are not indexed by spotlight) and likely some other opportunities to set the attribute directly with xattr if desired:

@n8henrie
Copy link

n8henrie commented Dec 14, 2023

Also, it appears that tmutil addexclusion supports multiple args:

$ mkdir -p {foo,bar}
$ ls
bar  foo
$ xattr *
$
$ tmutil addexclusion ./foo ./bar
$ xattr *
bar: com.apple.metadata:com_apple_backup_excludeItem
foo: com.apple.metadata:com_apple_backup_excludeItem

which means that many of the above commands could probably be done much more efficiently with find ... -exec tmutil addexclusion {} +, which collects arguments and runs the command once with collected arguments, as opposed to \; or piping to xargs / parallel, which will be separate invocations of tmutil.

EDIT: The equivalent for fd is --exec-batch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment