Skip to content

Instantly share code, notes, and snippets.

@coder0xff
Last active May 2, 2024 13:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coder0xff/ca41728fd55b0f74dd970ff46972cdbb to your computer and use it in GitHub Desktop.
Save coder0xff/ca41728fd55b0f74dd970ff46972cdbb to your computer and use it in GitHub Desktop.
dropbox_ignore.sh
#!/bin/bash
# This script sets or clears the Dropbox ignore attribute
# on files to reflect what is ignored by git.
# Usage: dropbox_ignore.sh [-n] [-q] [-v]"
# -n: Dry run"
# -q: Quiet"
# -v: Verbose"
# -n is a dry run. The script will not actually set or clear the ignore attribute,
# but will print what it would do.
# -q is quiet. The script will not print anything except errors.
# -v is verbose. The script will print debug information.
# The script uses the attr command to set and clear the Dropbox ignore attribute
# on files. The script uses the parallel command to speed up the process of
# checking the ignore attribute on files. The script uses the git command to
# determine what is ignored by git. Each of these commands must be installed.
# This script is intended to be run from within a git repository, either at the root
# of the repository or from a subdirectory. The script will not work if it is
# run outside a git repository. Nested git repositories will also be processes.
# Any git repository in the hieararchy may be include a .dropboxignore file in its
# root which will also ignore exactly matched files and directories (no glob support)
# as well as the ones ignored by git.
# Explanation of the script:
# 1. Collect the list of files and directories ignored by git.
# 2. Collect the list of all files and directories in the current directory,
# 3. Collect the list of all files and directories ignored by Dropbox, with some optimizations.
# 4. Apply or remove attributes as necessary.
set -e
# set -x
if ! command -v git &> /dev/null; then
echo "git command could not be found. Please install it." >&2
exit 1
fi
if ! command -v attr &> /dev/null; then
echo "attr command could not be found. Please install it." >&2
exit 1
fi
if ! command -v parallel &> /dev/null; then
echo "parallel command could not be found. Please install it." >&2
exit 1
fi
function notify() {
echo "$@"
}
function log() {
echo >/dev/null
}
function logf() {
echo >/dev/null
}
function set_ignore() {
notify "Setting ignore attribute on file: $1"
attr -s com.dropbox.ignored -V 1 "$1" 1>/dev/null
}
function clear_ignore() {
notify "Clearing ignore attribute on file: $1"
attr -r com.dropbox.ignored "$1"
}
function usage() {
echo "Usage: $0 [-n] [-q] [-v]"
echo " -n: Dry run"
echo " -q: Quiet"
echo " -v: Verbose"
echo " -h: Help"
}
while getopts "vnqh" opt; do
case $opt in
v)
function log() {
echo "$@"
}
function logf() {
printf "$@"
}
;;
n)
function set_ignore() {
echo "Dry run: Would set ignore attribute on file: $1"
}
function clear_ignore() {
echo "Dry run: Would clear ignore attribute on file: $1"
}
;;
q)
function notify() {
echo >/dev/null
}
;;
h)
usage
exit 0
;;
\?)
echo "Invalid option: -$opt" >&2
usage
exit 1
;;
esac
done
stack=()
# Push function to add an element to the stack
push_stack() {
stack+=("$1") # Adds element to the end of the array
}
# Pop function to remove the top element of the stack
pop_stack() {
if [ ${#stack[@]} -eq 0 ]; then
echo "Stack is empty" >&2 # Send error message to stderr
return 1
fi
unset stack[-1] # Remove the top element
stack=("${stack[@]}") # Re-index the array
}
# Peek function to return the top element of the stack without removing it
peek_stack() {
if [ ${#stack[@]} -eq 0 ]; then
echo "Stack is empty" >&2 # Send error message to stderr
return 1
fi
echo "${stack[-1]}" # Return the top element's value
}
function join_by {
local d=${1-} f=${2-}
if shift 2; then
printf %s "$f" "${@/#/$d}"
fi
}
push_stack ""
all_find_excludes=()
all_git_ignored=()
all_git_ignored_dirs=()
while [ ${#stack[@]} -gt 0 ]; do
dir=$(peek_stack)
pop_stack
if [[ -n "$dir" ]]; then
pushd "$dir" 1>/dev/null
prefix="$dir/"
log "Analyzing $dir"
else
prefix=""
log "Analyzing ."
fi
# Collect the list of files and directories ignored by git
readarray -t git_ignored < <(git clean -nXd | grep -E '^(Would remove |Would skip repository )' | sed -E 's/^Would remove //;s/^Would skip repository //')
log "Git ignored:"
logf "%s\n" "${git_ignored[@]}"
if [ -f ".dropboxignore" ]; then
log "Reading $prefix.dropboxignore"
while IFS= read -r line; do
# Append each line to the gitignored array
log $line
git_ignored+=("$line")
done < ".dropboxignore"
fi
git_ignored=($(printf "%s\n" "${git_ignored[@]}" | sort))
# and generated arguments to exclude them from the find command
find_excludes=("-path ./.git")
all_find_excludes+=("-path ./$prefix.git")
for file in "${git_ignored[@]}"; do
all_git_ignored+=("$prefix$file")
if [[ -d "$file" ]]; then
all_git_ignored_dirs+=("$prefix$file")
find_excludes+=("-path ./${file%/}")
all_find_excludes+=("-path ./$prefix${file%/}")
fi
done
find_excludes_str=$(join_by " -o " "${find_excludes[@]}")
for nested_git in $(find . \( $find_excludes_str \) -prune -o -type d -a -name .git -printf '%P\n' | xargs dirname 2>/dev/null | grep -v '^.$'); do
push_stack "$prefix$nested_git"
done
if [[ -n "$dir" ]]; then
popd 1>/dev/null
fi
done
all_git_ignored=($(printf "%s\n" "${all_git_ignored[@]}" | sort))
log ""
log "Git ignored dirs:"
logf "%s\n" "${all_git_ignored_dirs[@]}"
all_find_excludes_str=$(join_by " -o " "${all_find_excludes[@]}")
log ""
log "Executing find"
readarray -t found < <(find . \( $all_find_excludes_str \) -prune -o -name '*' -a ! -name '.' -printf '%P\n')
# Add the git-ignored directories to the list of found directories
# because they are not included in the find output, and we do
# actually need to check them.
for dir in "${all_git_ignored_dirs[@]}"; do
found+=("$dir")
done
log ""
log "Found files and directories:"
logf "%s\n" "${found[@]}"
# If the found count is greater than 1000, set PARALLEL_OPTS to "--bar" else set it to ""
if [[ ${#found[@]} -gt 10000 ]]; then
PARALLEL_OPTS="--bar"
else
PARALLEL_OPTS=""
fi
# Check the found files and directories for the Dropbox ignore attribute
readarray -t dropbox_ignored < <(printf "%s\n" "${found[@]}" | parallel $PARALLEL_OPTS 'if [[ "$(attr -q -g com.dropbox.ignored {} 2>/dev/null || echo 0)" != "0" ]]; then echo {}; fi' | sort)
log ""
log "Dropbox ignored files:" "${dropbox_ignored[@]}"
log ""
# Apply or remove attributes as necessary
for file in $(comm -13 <(printf "%s\n" "${dropbox_ignored[@]}") <(printf "%s\n" "${all_git_ignored[@]}")); do
set_ignore "$file"
done
log ""
for file in $(comm -23 <(printf "%s\n" "${dropbox_ignored[@]}") <(printf "%s\n" "${all_git_ignored[@]}")); do
clear_ignore "$file"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment