A bash script to zip uncommitted files in a local git repository

Terris Linenbach
3 min readDec 14, 2024

--

A cute fox next to a safe
A gratuitous image that wasted my time and erased future generations via CO2 emissions

Want to back up your work without pushing it to the remote repository due to the precommit hooks?

Run this script within a local git repository. All modified and new files in the repository are zipped to ~/.git-backups/<repo-name>-<branch-name>/<repo-name>-<branch-name>-<timestamp>.zip

Warning: This script ignores files that have been committed regardless of whether they’ve been pushed to the remote repository. If it’s not output by git status , it won’t be included in the archive.

This script runs fine on MacOS (which always requires extra work!) and it *should* run on Linux but I haven’t tried it.

Preparation

  • MacOS: brew install zip
  • Copy the script below to (for example) ~/gitzip
  • chmod +x ~/gitzip
#!/bin/bash

# Run this script within a local git repository.
# All modified and new files in the repository are zipped to
# ~/.git-backups/<repository>-<branch>/<repository>-<branch>-<timestamp>.zip

# Exit immediately if a command exits with a non-zero status
set -e

# Function to print error messages to stderr and exit
error() {
echo "Error: $1" >&2
exit 1
}

# Function to generate a timestamp in the format YYYYMMDD-HHMMSS
generate_timestamp() {
date +"%Y%m%d-%H%M%S"
}

# Function to sanitize strings for use in file and directory names
sanitize() {
echo "$1" | tr ' /\\:*?"<>|' '----------'
}

# Change to the root of the Git repository
git_root=$(git rev-parse --show-toplevel 2>/dev/null) || error "Not inside a Git repository."
cd "$git_root" || error "Failed to change directory to Git root."

# Retrieve the repository name using the basename of the Git root directory
repo_name=$(basename "$git_root")
repo_name_sanitized=$(sanitize "$repo_name")

# Get the current Git branch name
branch_name=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || error "Failed to retrieve current Git branch."
branch_name_sanitized=$(sanitize "$branch_name")

# Get the list of changed files (staged and unstaged)
changed_files=$(git diff --name-only HEAD)

# Get the list of untracked files
untracked_files=$(git ls-files --others --exclude-standard)

# Combine changed and untracked files, ensuring no duplicates
combined_files=$(printf "%s\n%s" "$changed_files" "$untracked_files" | sort -u)

# Check if there are any files to back up
if [ -z "$combined_files" ]; then
echo "No changed or untracked files found."
exit 0
fi

# Create a temporary directory for copying changed and untracked files
temp_dir=$(mktemp -d) || error "Failed to create temporary directory."

# Ensure the temporary directory is removed on script exit
trap 'rm -rf "$temp_dir"' EXIT

# Copy changed and untracked files to the temporary directory, preserving directory structure
while IFS= read -r file; do
# Ensure the file exists (it might have been deleted)
if [ -e "$file" ]; then
# Create the target directory inside temp_dir
target_dir="$temp_dir/$(dirname "$file")"
mkdir -p "$target_dir" || error "Failed to create directory structure for $file."

# Copy the file to the target directory
cp "$file" "$target_dir/" || error "Failed to copy file: $file"
else
echo "Skipping deleted file: $file"
fi
done <<< "$combined_files"

# Create the zip file with the repository and branch name
zip_file="${repo_name_sanitized}-${branch_name_sanitized}-$(generate_timestamp).zip"
(
cd "$temp_dir" || error "Failed to change directory to temporary directory."
zip -r "$zip_file" ./* >/dev/null || error "Failed to create zip archive."
)

# Define the backup directory path
backup_dir="$HOME/.git-backups/${repo_name_sanitized}-${branch_name_sanitized}"

# Create the backup directory
mkdir -p "$backup_dir" || error "Failed to create backup directory: $backup_dir"

# Move the zip file to the backup directory
mv "$temp_dir/$zip_file" "$backup_dir/" || error "Failed to move zip file."

# Inform the user of the backup location
echo "Changed and untracked files have been zipped to $backup_dir/$zip_file"

--

--

Terris Linenbach
Terris Linenbach

Written by Terris Linenbach

He/him. Coder since 1980. Always seeking the Best Way. CV: https://terris.com/cv

No responses yet