Skip to content

Fix: Resolve git-lfs and husky hooks conflict in devcontainer setup #481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
665adad
remove forced git lfs install
aicodexp Aug 6, 2025
92023e8
disable autopull to prevent
aicodexp Aug 6, 2025
0501534
remove obsolete pre-push hook
aicodexp Aug 6, 2025
c684638
add git-lfs feature to devcontainer lock
aicodexp Aug 6, 2025
36cc63d
script merges husky hooks with lfs hook content
aicodexp Aug 6, 2025
94700ac
fix: enhance merging of Git LFS hooks with existing Husky hooks
aicodexp Aug 6, 2025
7d58d38
add Git LFS configuration check to hook validation
aicodexp Aug 6, 2025
3ce9cc1
improve error handling for Git LFS installation in auto mode
aicodexp Aug 6, 2025
691e591
enhance Git LFS hook generation with specific markers for better inte…
aicodexp Aug 6, 2025
b80e479
update postCreateCommand to ensure correct directory context for LFS …
aicodexp Aug 6, 2025
28b0a1f
improve compatibility of LFS and Husky hooks by adjusting exit codes …
aicodexp Aug 6, 2025
325d208
add error handling during LFS pull
aicodexp Aug 6, 2025
7bb0767
add backup check for existing Husky hooks before creating backups
aicodexp Aug 6, 2025
eb11b4c
improve existing content extraction in merge_hooks function
aicodexp Aug 6, 2025
c7c3859
fix: improve header extraction and add error handling for hook permis…
aicodexp Aug 6, 2025
42a85a2
correct backup directory path for Husky hooks
aicodexp Aug 6, 2025
184f6e3
fix Git LFS hook merging
aicodexp Aug 6, 2025
a939a1e
fix: update backup and merge logic to use HUSKY_TEMPLATES_DIR for hooks
aicodexp Aug 6, 2025
4582ea9
fix: streamline argument processing and improve error handling in LFS…
aicodexp Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"resolved": "ghcr.io/devcontainers/features/dotnet@sha256:06f4ef2c23792da4832a74da195d478d8f64316c45c7624a0367d6bd5c3fc500",
"integrity": "sha256:06f4ef2c23792da4832a74da195d478d8f64316c45c7624a0367d6bd5c3fc500"
},
"ghcr.io/devcontainers/features/git-lfs:1": {
"version": "1.2.5",
"resolved": "ghcr.io/devcontainers/features/git-lfs@sha256:71c2b371cf12ab7fcec47cf17369c6f59156100dad9abf9e4c593049d789de72",
"integrity": "sha256:71c2b371cf12ab7fcec47cf17369c6f59156100dad9abf9e4c593049d789de72"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/python@sha256:cf9b6d879790a594b459845b207c5e1762a0c8f954bb8033ff396e497f9c301b",
Expand Down
8 changes: 5 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"ghcr.io/devcontainers/features/python:1": {},
"ghcr.io/devcontainers/features/dotnet:2": {},
"ghcr.io/devcontainers/features/desktop-lite:1": {},
"ghcr.io/devcontainers/features/git-lfs:1": {}
"ghcr.io/devcontainers/features/git-lfs:1": {
"autoPull": false // see https://github.com/devcontainers/features/blob/main/src/git-lfs/README.md#options
}
},
"containerEnv": {
"DEBIAN_FRONTEND": "noninteractive"
Expand All @@ -30,7 +32,7 @@
},
"containerUser": "node",
"onCreateCommand": {
"initGitLfs": "git lfs install --force",
"npmInstall": "npm install || true"
}
},
"postCreateCommand": "cd ${containerWorkspaceFolder} && bash .devcontainer/fix-lfs-husky-hooks.sh --autoMergeLfsHusky && (git lfs pull || echo 'Warning: git lfs pull failed')"
}
199 changes: 199 additions & 0 deletions .devcontainer/fix-lfs-husky-hooks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#!/usr/bin/env bash
# Git LFS and Husky Integration Script
#
# PURPOSE: Resolves conflicts between Git LFS and Husky hook management
# by merging Git LFS hooks into Husky's hook structure.
#
# PROBLEM: When using both Git LFS and Husky in a project, their hook management
# systems can conflict. Husky sets core.hooksPath to .husky/_, but Git LFS
# expects hooks in the standard location or its own registered location.
#
# USAGE:
# ./fix-lfs-husky-hooks.sh # Interactive mode
# ./fix-lfs-husky-hooks.sh --auto # Automatic mode, no prompts

set -eo pipefail

# Configuration
HUSKY_DIR=".husky"
HUSKY_TEMPLATES_DIR="$HUSKY_DIR/_"
BACKUP_DIR="$HUSKY_DIR/_backups/$(date +%Y%m%d_%H%M%S)"
HOOKS=("pre-push" "post-checkout" "post-commit" "post-merge")
AUTO_MODE=false

# Process arguments
case "${1:-}" in
--auto|--autoMergeLfsHusky)
AUTO_MODE=true
;;
--help|-h)
cat << EOF
Usage: $0 [--auto] [--help]

Options:
--auto Run in automatic mode without prompts
--help Show this help message
EOF
exit 0
;;
"")
# No arguments, continue with interactive mode
;;
*)
echo "Unknown option: $1" >&2
echo "Use --help for usage information" >&2
exit 1
;;
esac

is_git_lfs_configured() {
# Check for .gitattributes with LFS filters
[ -f ".gitattributes" ] && grep -q "filter=lfs" ".gitattributes" && return 0

# Check if any LFS pointers exist in the repo
git lfs ls-files 2>/dev/null | grep -q . && return 0

# Check Git config for LFS settings
git config --local --get-regexp "lfs" | grep -q . && return 0

return 1
}

validate_environment() {
# Check if we're in a git repository
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
echo "Error: Not in a git repository" >&2
exit 1
}

# Check if Husky is configured
[ -d "$HUSKY_DIR" ] || {
echo "Error: Husky directory '$HUSKY_DIR' not found" >&2
exit 1
}

# Check if Git LFS is installed
if ! command -v git-lfs >/dev/null 2>&1; then
echo "Warning: Git LFS is not installed" >&2
if [ "$AUTO_MODE" = false ]; then
read -p "Continue anyway? (y/N): " confirm
[[ "$confirm" == [yY] ]] || exit 0
else
echo "Error: Git LFS is not installed and --auto mode is enabled" >&2
exit 1
fi
fi

# Check if Git LFS is actually configured in this repository
if ! is_git_lfs_configured; then
echo "Warning: Git LFS does not appear to be configured in this repository" >&2
if [ "$AUTO_MODE" = false ]; then
read -p "Continue anyway? (y/N): " confirm
[[ "$confirm" == [yY] ]] || exit 0
else
echo "Skipping hook modifications as Git LFS is not configured"
exit 0
fi
fi
}

# Create backup of existing hooks that need modification
backup_hooks() {
local needs_backup=false

for hook in "${HOOKS[@]}"; do
if [ -f "$HUSKY_TEMPLATES_DIR/$hook" ] && ! grep -q "=== BEGIN GIT LFS HOOK" "$HUSKY_TEMPLATES_DIR/$hook"; then
needs_backup=true
break
fi
done

[ "$needs_backup" = false ] && {
echo "No backup needed - all hooks already configured"
return
}

echo "Creating backup in $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"

for hook in "${HOOKS[@]}"; do
[ -f "$HUSKY_TEMPLATES_DIR/$hook" ] && {
cp "$HUSKY_TEMPLATES_DIR/$hook" "$BACKUP_DIR/$hook"
echo "✓ Backed up $hook"
}
done
}

# Generate Git LFS hook content
generate_lfs_hook() {
local hook_type="$1"
cat << EOF
# === BEGIN GIT LFS HOOK (added by fix-lfs-husky-hooks.sh) ===
command -v git-lfs >/dev/null 2>&1 || {
printf >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path.\n"
printf >&2 "If you no longer wish to use Git LFS, remove this hook by deleting the '$hook_type' file.\n\n"
exit 2
}
git lfs $hook_type "\$@"
# === END GIT LFS HOOK ===
EOF
}

merge_hooks() {
echo "Merging Git LFS hooks with Husky hooks..."

for hook in "${HOOKS[@]}"; do
local hook_file="$HUSKY_TEMPLATES_DIR/$hook"

# Skip if already configured
if [ -f "$hook_file" ] && grep -q "=== BEGIN GIT LFS HOOK (added by fix-lfs-husky-hooks.sh) ===" "$hook_file"; then
echo "✓ $hook: Already configured"
continue
fi

# Generate LFS hook content
local lfs_content
lfs_content=$(generate_lfs_hook "$hook")

if [ -f "$hook_file" ]; then
# Merge with existing hook (LFS content before Husky initialization)
{
echo '#!/usr/bin/env sh'
echo "$lfs_content"
echo ""
tail -n +2 "$hook_file" # Skip shebang line
} > "$hook_file.new" && mv "$hook_file.new" "$hook_file"
echo "✓ $hook: Merged with existing hook"
else
# Create new hook
{
echo '#!/usr/bin/env sh'
echo "$lfs_content"
echo ""
echo '. "$(dirname "$0")/h"'
} > "$hook_file"
echo "✓ $hook: Created new hook"
fi

chmod +x "$hook_file" || echo "⚠ Warning: Could not make $hook executable"
done
}

# Main execution
main() {
echo "Git LFS and Husky Integration Script"

validate_environment

if [ "$AUTO_MODE" = false ]; then
read -p "This will modify Husky hooks to include Git LFS functionality. Continue? (y/N): " confirm
[[ "$confirm" == [yY] ]] || exit 0
fi

backup_hooks
merge_hooks

echo "✅ Integration complete! You can now use Git LFS commands normally."
}

main
5 changes: 0 additions & 5 deletions .husky/pre-push

This file was deleted.