Last active
May 2, 2024 13:09
-
-
Save coder0xff/ca41728fd55b0f74dd970ff46972cdbb to your computer and use it in GitHub Desktop.
dropbox_ignore.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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