Skip to content

Git LFS Config Generator

Generates smart .gitattributes configuration for Git LFS based on file sizes

Metadata

  • Author: ropean
  • Version: 1.0.0

Code

bash
#!/bin/bash

# @title Git LFS Config Generator
# @description Generates smart .gitattributes configuration for Git LFS based on file sizes
# @author ropean
# @version 1.0.0
# @date 2025-10-30
#
# SYNOPSIS
#     Generates a smart .gitattributes configuration for Git LFS based on file sizes in a repository.
#
# DESCRIPTION
#     This script scans a Git repository for large files and automatically generates appropriate Git LFS
#     configurations. It identifies file extensions of large files and creates corresponding entries in
#     the .gitattributes file. The script can either create a new configuration or append to an existing one.
#
# PARAMETERS
#     -r, --repo-path PATH     The path to the Git repository to scan. Defaults to current directory.
#     -s, --min-size SIZE      The minimum file size in megabytes to consider for Git LFS tracking. Defaults to 5MB.
#     -o, --output-file FILE   The name of the output .gitattributes file. Defaults to ".gitattributes".
#     -a, --append             If specified, appends new configurations to an existing .gitattributes file.
#     -h, --help               Display this help message.
#
# EXAMPLES
#     ./git-lfs-config-generator.sh
#     Scans the current directory for files larger than 5MB and creates a new .gitattributes file.
#
#     ./git-lfs-config-generator.sh -r /path/to/repo -s 10 -a
#     Scans /path/to/repo for files larger than 10MB and appends configurations to existing .gitattributes.
#
# NOTES
#     Author: Converted from PowerShell
#     Last Updated: 2025-10-30

# Default values
REPO_PATH="$(pwd)"
MIN_SIZE_MB=5
OUTPUT_FILE=".gitattributes"
APPEND_MODE=false

# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# Help function
show_help() {
	cat <<EOF
Usage: $(basename "$0") [OPTIONS]

Generates a smart .gitattributes configuration for Git LFS based on file sizes.

OPTIONS:
    -r, --repo-path PATH     Repository path (default: current directory)
    -s, --min-size SIZE      Minimum file size in MB (default: 5)
    -o, --output-file FILE   Output file name (default: .gitattributes)
    -a, --append             Append to existing file
    -h, --help               Show this help message

EXAMPLES:
    $(basename "$0")
    $(basename "$0") -r /path/to/repo -s 10 -a
EOF
	exit 0
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
	case $1 in
	-r | --repo-path)
		REPO_PATH="$2"
		shift 2
		;;
	-s | --min-size)
		MIN_SIZE_MB="$2"
		shift 2
		;;
	-o | --output-file)
		OUTPUT_FILE="$2"
		shift 2
		;;
	-a | --append)
		APPEND_MODE=true
		shift
		;;
	-h | --help)
		show_help
		;;
	*)
		echo -e "${RED}Error: Unknown option $1${NC}"
		show_help
		;;
	esac
done

# If no repo path specified, ask user
if [[ "$REPO_PATH" == "$(pwd)" ]]; then
	echo -n "Enter repository path (press Enter to use current directory): "
	read -r user_input
	if [[ -n "$user_input" ]]; then
		REPO_PATH="$user_input"
	fi
fi

# Validate repository path
if [[ ! -d "$REPO_PATH" ]]; then
	echo -e "${RED}Error: Repository path does not exist: $REPO_PATH${NC}"
	exit 1
fi

if [[ ! -d "$REPO_PATH/.git" ]]; then
	echo -e "${YELLOW}Warning: The specified path may not be a Git repository (.git folder not found)${NC}"
	echo -n "Do you want to continue anyway? (Y/N): "
	read -r continue
	if [[ "$continue" != "Y" && "$continue" != "y" ]]; then
		exit 0
	fi
fi

# Initialize
MIN_SIZE_BYTES=$((MIN_SIZE_MB * 1024 * 1024))

# Change to repository directory
cd "$REPO_PATH" || exit 1

echo -e "${CYAN}=== Git LFS Configuration Generator ===${NC}"
echo -e "${YELLOW}Scanning for files larger than ${MIN_SIZE_MB}MB...${NC}"
echo ""

# Scan for large files and collect statistics
declare -A file_stats_count
declare -A file_stats_size

while IFS= read -r -d '' file; do
	# Skip .git directory
	if [[ "$file" == *"/.git/"* ]]; then
		continue
	fi

	file_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)

	if [[ $file_size -gt $MIN_SIZE_BYTES ]]; then
		ext="${file##*.}"
		ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')

		if [[ -n "$ext" && "$ext" != "$file" ]]; then
			ext=".$ext"
			file_stats_count["$ext"]=$((${file_stats_count["$ext"]:-0} + 1))
			file_stats_size["$ext"]=$((${file_stats_size["$ext"]:-0} + file_size))
		fi
	fi
done < <(find . -type f -print0 2>/dev/null)

# Check if any large files were found
if [[ ${#file_stats_count[@]} -eq 0 ]]; then
	echo -e "${YELLOW}No files larger than ${MIN_SIZE_MB}MB found${NC}"
	exit 0
fi

# Display statistics
echo -e "${GREEN}Found the following large file types:${NC}"
echo ""
echo -e "${CYAN}Extension\tCount\tTotal Size${NC}"
echo "----------------------------------------"

for ext in $(printf '%s\n' "${!file_stats_count[@]}" | sort); do
	count=${file_stats_count["$ext"]}
	size_mb=$(echo "scale=2; ${file_stats_size["$ext"]} / 1048576" | bc)
	printf "%s\t\t%s\t\t%s MB\n" "$ext" "$count" "$size_mb"
done
echo ""

# Read existing configuration if append mode
declare -A existing_rules
output_path="$REPO_PATH/$OUTPUT_FILE"

if [[ "$APPEND_MODE" == true && -f "$output_path" ]]; then
	echo -e "${YELLOW}Detected existing configuration, merging...${NC}"

	while IFS= read -r line; do
		if [[ "$line" =~ ^\*(\.[a-zA-Z0-9]+)[[:space:]]+filter=lfs ]]; then
			ext="${BASH_REMATCH[1]}"
			existing_rules["$ext"]="$line"
		fi
	done <"$output_path"
fi

# Generate new rules
declare -A new_rules

for ext in "${!file_stats_count[@]}"; do
	count=${file_stats_count["$ext"]}
	size_mb=$(echo "scale=2; ${file_stats_size["$ext"]} / 1048576" | bc)
	rule="# $count files, ${size_mb}MB"$'\n'"*$ext filter=lfs diff=lfs merge=lfs -text"
	new_rules["$ext"]="$rule"
done

# Merge rules (new rules take precedence)
declare -A final_rules

for ext in "${!existing_rules[@]}"; do
	final_rules["$ext"]="${existing_rules["$ext"]}"
done

for ext in "${!new_rules[@]}"; do
	final_rules["$ext"]="${new_rules["$ext"]}"
done

# Generate final content
current_date=$(date "+%Y-%m-%d %H:%M:%S")
content="# Git LFS Configuration
# Generated: $current_date
# Scan criteria: File size > ${MIN_SIZE_MB}MB

"

# Add rules sorted by extension
for ext in $(printf '%s\n' "${!final_rules[@]}" | sort); do
	content+="${final_rules["$ext"]}"$'\n'
done

# Write to file
echo "$content" >"$output_path"

echo -e "${GREEN}✅ Configuration file generated: $output_path${NC}"

if [[ "$APPEND_MODE" == true && ${#existing_rules[@]} -gt 0 ]]; then
	new_count=0
	for ext in "${!new_rules[@]}"; do
		if [[ -z "${existing_rules["$ext"]}" ]]; then
			((new_count++))
		fi
	done
	echo -e "${CYAN}   Preserved ${#existing_rules[@]} existing rules${NC}"
	echo -e "${CYAN}   Added $new_count new rules${NC}"
fi

echo ""
echo -e "${YELLOW}File content preview:${NC}"
echo "----------------------------------------"
cat "$output_path"
echo "----------------------------------------"

File Information

  • Filename: git-lfs-config-generator.sh
  • Category: git
  • Language: BASH

View on GitHub