Skip to content

Instantly share code, notes, and snippets.

@robbiemu
Created June 14, 2025 19:48
Show Gist options
  • Save robbiemu/c31f1324f8b2480c64530e492ff5fc34 to your computer and use it in GitHub Desktop.
Save robbiemu/c31f1324f8b2480c64530e492ff5fc34 to your computer and use it in GitHub Desktop.
zip_project - Zip up source and documentation files from a Git repo, excluding .git, .venv, and anything in .gitignore.
#!/bin/bash
# zip_project.sh - Zip up source and documentation files from a Git repo,
# excluding .git, .venv, and anything in .gitignore. Use --help for options.
#
# Usage:
# ./zip_project.sh [--output FILE] [--verbose] [--filter [REGEX]] [--exclude REGEX] [--help]
#
# Options:
# -o, --output FILE Name of the output zip file (default: project_bundle.zip)
# -v, --verbose Print verbose output
# -f, --filter [REGEX] Only include files matching REGEX. If REGEX is omitted,
# default to code + markdown extensions.
# -e, --exclude REGEX Exclude files matching REGEX from the archive
# -h, --help Show this help message and exit
ZIPFILE="project_bundle.zip"
VERBOSE=0
FILTER=0
FILTER_PATTERN=''
EXCLUDE_PATTERN=''
DEFAULT_FILTER='\.(py|js|ts|go|java|rb|rs|cpp|c|md|sh|json|yml|yaml|toml|ipynb)$'
print_help_short() {
tail -n +2 "$0" | sed -n 's/^# \{0,1\}//p' | sed '/^$/q'
}
print_help() {
awk '
NR==1 && /^#!/ { next }
/^#/ { sub(/^# ?/, ""); print; next }
{ exit }
' "$0"
}
log() {
if [ "$VERBOSE" = "1" ]; then
echo "[zip_project] $1"
fi
}
# Manual argument parser
while [ "$#" -gt 0 ]; do
case "$1" in
-o|--output)
ZIPFILE="$2"
shift 2
;;
-v|--verbose)
VERBOSE=1
shift
;;
-f|--filter)
FILTER=1
shift
if [ "$#" -gt 0 ] && echo "$1" | grep -v '^-' > /dev/null; then
FILTER_PATTERN="$1"
shift
fi
;;
-e|--exclude)
shift
if [ "$#" -gt 0 ] && ! echo "$1" | grep -q '^-' ; then
EXCLUDE_PATTERN="$1"
shift
else
echo "Error: --exclude requires a regex pattern argument" >&2
exit 1
fi
;;
-h|--help)
print_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
print_help_short
exit 1
;;
esac
done
log "Output zip will be: $ZIPFILE"
TMPDIR=$(mktemp -d "/tmp/zip_project.XXXXXX") || exit 1
log "Using temporary directory: $TMPDIR"
git ls-files --cached --others --exclude-standard > "$TMPDIR/all_files.txt"
if [ "$FILTER" = "1" ]; then
if [ -z "$FILTER_PATTERN" ]; then
FILTER_PATTERN="$DEFAULT_FILTER"
log "Filtering with default pattern: $FILTER_PATTERN"
else
log "Filtering with custom pattern: $FILTER_PATTERN"
fi
if [ -n "$EXCLUDE_PATTERN" ]; then
log "Excluding files matching: $EXCLUDE_PATTERN"
egrep "$FILTER_PATTERN" "$TMPDIR/all_files.txt" | grep -v -E "$EXCLUDE_PATTERN" > "$TMPDIR/filelist.txt"
else
egrep "$FILTER_PATTERN" "$TMPDIR/all_files.txt" > "$TMPDIR/filelist.txt"
fi
else
if [ -n "$EXCLUDE_PATTERN" ]; then
log "Excluding files matching: $EXCLUDE_PATTERN"
grep -v -E "$EXCLUDE_PATTERN" "$TMPDIR/all_files.txt" > "$TMPDIR/filelist.txt"
else
cp "$TMPDIR/all_files.txt" "$TMPDIR/filelist.txt"
fi
fi
log "Copying files to staging..."
mkdir -p "$TMPDIR/staging"
while read file; do
dir=$(dirname "$file")
mkdir -p "$TMPDIR/staging/$dir"
cp "$file" "$TMPDIR/staging/$file"
done < "$TMPDIR/filelist.txt"
log "Creating zip archive..."
OUTDIR=$(pwd)
(cd "$TMPDIR/staging" && zip -9rq "$OUTDIR/$ZIPFILE" .)
rm -rf "$TMPDIR"
echo "✅ Created zip archive: $ZIPFILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment