Skip to content

Commit 1631468

Browse files
git-clean-ignore improvements?
1 parent 56e88ac commit 1631468

1 file changed

Lines changed: 227 additions & 80 deletions

File tree

bin/git-clean-ignore

Lines changed: 227 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@
55
# This script scans a Git repository for files that match patterns in .gitignore
66
# but are still being tracked. It prompts the user to either remove these files
77
# from tracking or remove the corresponding patterns from .gitignore.
8-
9-
set -e
8+
#
9+
# Usage:
10+
# git-clean-ignore [options]
11+
#
12+
# Options:
13+
# --verbose Show detailed information about all patterns
14+
# --clean repo Automatically remove ignored files from tracking
15+
# --clean gitignore Remove patterns from .gitignore that match tracked files
16+
# --help Show this help message
1017

1118
# Text formatting
1219
bold=$(tput bold)
@@ -16,7 +23,26 @@ green=$(tput setaf 2)
1623
yellow=$(tput setaf 3)
1724
blue=$(tput setaf 4)
1825

26+
# Default options
27+
VERBOSE=false
28+
AUTO_CLEAN_REPO=false
29+
AUTO_CLEAN_GITIGNORE=false
30+
1931
# Helper functions
32+
function print_help() {
33+
echo "${bold}Git Ignore Cleanup Utility${normal}"
34+
echo ""
35+
echo "Usage:"
36+
echo " $(basename $0) [options]"
37+
echo ""
38+
echo "Options:"
39+
echo " --verbose Show detailed information about all patterns"
40+
echo " --clean repo Automatically remove ignored files from tracking"
41+
echo " --clean gitignore Remove patterns from .gitignore that match tracked files"
42+
echo " --help Show this help message"
43+
exit 0
44+
}
45+
2046
function print_header() {
2147
echo "\n${bold}${blue}$1${normal}\n"
2248
}
@@ -67,122 +93,243 @@ function get_repo_root() {
6793
git rev-parse --show-toplevel
6894
}
6995

96+
# Parse command line arguments
97+
for arg in "$@"; do
98+
case "$arg" in
99+
--verbose)
100+
VERBOSE=true
101+
;;
102+
--clean)
103+
echo "Error: The --clean option requires an argument (repo or gitignore)"
104+
exit 1
105+
;;
106+
--clean=*)
107+
CLEAN_TARGET=${arg#*=}
108+
if [[ "$CLEAN_TARGET" == "repo" ]]; then
109+
AUTO_CLEAN_REPO=true
110+
elif [[ "$CLEAN_TARGET" == "gitignore" ]]; then
111+
AUTO_CLEAN_GITIGNORE=true
112+
else
113+
echo "Error: Invalid --clean target: $CLEAN_TARGET"
114+
echo "Valid targets are 'repo' or 'gitignore'"
115+
exit 1
116+
fi
117+
;;
118+
--clean\ repo)
119+
AUTO_CLEAN_REPO=true
120+
;;
121+
--clean\ gitignore)
122+
AUTO_CLEAN_GITIGNORE=true
123+
;;
124+
--help)
125+
print_help
126+
;;
127+
*)
128+
echo "Unknown option: $arg"
129+
print_help
130+
;;
131+
esac
132+
shift
133+
done
134+
70135
# Validate requirements
71136
if ! is_git_repo; then
72137
print_error "Not a Git repository. Please run this script from within a Git repository."
73138
exit 1
74139
fi
75140

76-
if [[ ! -f ".gitignore" ]]; then
77-
print_error "No .gitignore file found in the current directory."
78-
exit 1
79-
fi
80-
81141
REPO_ROOT=$(get_repo_root)
82142
GITIGNORE_FILE="${REPO_ROOT}/.gitignore"
83143

84-
print_header "Git Ignore Synchronization Tool"
85-
echo "This tool will help you synchronize your .gitignore patterns with tracked files."
86-
echo "It will find files that are currently tracked but match ignore patterns."
144+
if [[ ! -f "$GITIGNORE_FILE" ]]; then
145+
print_error "No .gitignore file found at $GITIGNORE_FILE"
146+
exit 1
147+
fi
148+
149+
# Initial message
150+
if $VERBOSE; then
151+
print_header "Git Ignore Synchronization Tool"
152+
echo "This tool will help you synchronize your .gitignore patterns with tracked files."
153+
echo "It will find files that are currently tracked but match ignore patterns."
154+
fi
87155

88-
# Process each non-empty, non-comment line in .gitignore
89-
print_header "Scanning .gitignore patterns..."
156+
# Get all ignored but tracked files in one go first
157+
print_header "Finding tracked files that should be ignored..."
90158

91159
TOTAL_MATCHED_FILES=0
92160
PATTERNS_TO_REMOVE=()
93-
PROCESSED_PATTERNS=()
161+
MATCHED_PATTERNS=()
162+
MATCHED_FILES_MAP=()
94163

95-
# Read .gitignore line by line
96-
while IFS= read -r line || [[ -n "$line" ]]; do
97-
# Skip empty lines and comments
98-
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
99-
continue
100-
fi
101-
102-
# Extract the actual pattern (ignore inline comments)
103-
pattern=$(echo "$line" | sed 's/\s*#.*$//')
104-
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
105-
106-
# Skip if pattern is empty or already processed
107-
if [[ -z "$pattern" || " ${PROCESSED_PATTERNS[@]} " =~ " ${pattern} " ]]; then
108-
continue
109-
fi
110-
111-
PROCESSED_PATTERNS+=("$pattern")
112-
113-
echo "Checking pattern: ${bold}$pattern${normal}"
114-
115-
# Check if any tracked files match this pattern
116-
matched_files=$(git ls-files --cached -i --exclude="$pattern" 2>/dev/null)
117-
if [[ -z "$matched_files" ]]; then
118-
continue
119-
fi
164+
# Collect all ignored files that are still tracked
165+
all_tracked_ignored_files=$(git ls-files --cached -i --exclude-from="$GITIGNORE_FILE" 2>/dev/null)
166+
167+
if [[ -z "$all_tracked_ignored_files" ]]; then
168+
print_success "No tracked files matching ignore patterns were found."
169+
echo "Your repository is in sync with your .gitignore file!"
170+
exit 0
171+
fi
172+
173+
# Count matched files
174+
matched_files_count=$(echo "$all_tracked_ignored_files" | wc -l | tr -d ' ')
175+
print_warning "Found ${bold}$matched_files_count${normal} tracked files that match patterns in .gitignore:"
176+
echo "$all_tracked_ignored_files" | sed 's/^/ - /'
177+
echo
178+
179+
# Process each file to map it to the pattern that's ignoring it
180+
while read -r file; do
181+
matched_pattern=""
182+
while IFS= read -r line || [[ -n "$line" ]]; do
183+
# Skip empty lines and comments
184+
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
185+
continue
186+
fi
187+
188+
# Extract the pattern (ignore inline comments)
189+
pattern=$(echo "$line" | sed 's/\s*#.*$//')
190+
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
191+
192+
if [[ -z "$pattern" ]]; then
193+
continue
194+
fi
195+
196+
# Check if this pattern matches the file
197+
if git check-ignore -q --no-index --exclude="$pattern" "$file" 2>/dev/null; then
198+
matched_pattern="$pattern"
199+
if ! [[ " ${MATCHED_PATTERNS[@]} " =~ " ${pattern} " ]]; then
200+
MATCHED_PATTERNS+=("$pattern")
201+
MATCHED_FILES_MAP+=("$pattern:$file")
202+
else
203+
MATCHED_FILES_MAP+=("$pattern:$file")
204+
fi
205+
break
206+
fi
207+
done < "$GITIGNORE_FILE"
208+
done <<< "$all_tracked_ignored_files"
209+
210+
# Now handle each pattern with its matched files
211+
for pattern in "${MATCHED_PATTERNS[@]}"; do
212+
# Get all files matching this pattern
213+
matched_files=""
214+
for entry in "${MATCHED_FILES_MAP[@]}"; do
215+
IFS=':' read -r p f <<< "$entry"
216+
if [[ "$p" == "$pattern" ]]; then
217+
matched_files="$matched_files$f\n"
218+
fi
219+
done
220+
matched_files=$(echo "$matched_files" | sed '/^$/d')
120221

121-
# Count matched files
222+
# Count matched files for this pattern
122223
matched_count=$(echo "$matched_files" | wc -l | tr -d ' ')
123-
TOTAL_MATCHED_FILES=$((TOTAL_MATCHED_FILES + matched_count))
124224

125-
echo "Found ${bold}$matched_count${normal} tracked files matching pattern:"
126-
echo "$matched_files" | sed 's/^/ - /'
225+
if $VERBOSE; then
226+
echo "Pattern: ${bold}$pattern${normal} matches ${bold}$matched_count${normal} files:"
227+
echo "$matched_files" | sed 's/^/ - /'
228+
echo
229+
else
230+
echo "Pattern: ${bold}$pattern${normal} matches ${bold}$matched_count${normal} files."
231+
fi
127232

128-
# Prompt to remove files from tracking
129-
if ask_yes_no "Remove these files from Git tracking (they will remain on disk)?" "y"; then
130-
echo "$matched_files" | xargs git rm --cached
233+
# Handle auto-clean modes or prompt user
234+
if $AUTO_CLEAN_REPO; then
235+
echo "Removing matched files from tracking (--clean repo specified)..."
236+
echo "$matched_files" | xargs git rm --cached -q
131237
print_success "Files removed from tracking but kept on disk."
238+
elif $AUTO_CLEAN_GITIGNORE; then
239+
PATTERNS_TO_REMOVE+=("$pattern")
240+
print_warning "Pattern '$pattern' marked for removal from .gitignore (--clean gitignore specified)."
132241
else
133-
# If user doesn't want to remove files, offer to remove the pattern
134-
if ask_yes_no "Keep these files tracked? (This will remove the pattern from .gitignore)" "n"; then
135-
PATTERNS_TO_REMOVE+=("$pattern")
136-
print_warning "Pattern '$pattern' marked for removal from .gitignore."
242+
# Interactive mode - prompt the user
243+
echo "Found ${bold}$matched_count${normal} tracked files matching pattern: ${bold}$pattern${normal}"
244+
if $VERBOSE; then
245+
echo "$matched_files" | sed 's/^/ - /'
137246
else
138-
print_warning "No action taken for pattern '$pattern'."
247+
echo "Use --verbose to see the list of files"
139248
fi
249+
250+
echo "Choose an action:"
251+
echo " 1) Remove these files from Git tracking (keep on disk)"
252+
echo " 2) Keep tracking these files (remove pattern from .gitignore)"
253+
echo " 3) Skip this pattern (no action)"
254+
read -r "choice?Your choice [1-3]: "
255+
256+
case "$choice" in
257+
1)
258+
echo "Removing files from tracking..."
259+
echo "$matched_files" | xargs git rm --cached -q
260+
print_success "Files removed from tracking but kept on disk."
261+
;;
262+
2)
263+
PATTERNS_TO_REMOVE+=("$pattern")
264+
print_warning "Pattern '$pattern' marked for removal from .gitignore."
265+
;;
266+
3)
267+
print_warning "No action taken for pattern '$pattern'."
268+
;;
269+
*)
270+
print_error "Invalid choice. Skipping this pattern."
271+
;;
272+
esac
140273
fi
141274

142275
echo ""
143-
done < "$GITIGNORE_FILE"
276+
done
144277

145278
# Remove patterns from .gitignore if needed
146279
if [[ ${#PATTERNS_TO_REMOVE[@]} -gt 0 ]]; then
147280
print_header "Updating .gitignore file..."
148281

149-
temp_file=$(mktemp)
150-
while IFS= read -r line || [[ -n "$line" ]]; do
151-
pattern_match=0
152-
153-
# Skip empty lines and comments
154-
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
155-
echo "$line" >> "$temp_file"
156-
continue
157-
fi
158-
159-
# Extract the pattern (ignore inline comments)
160-
pattern=$(echo "$line" | sed 's/\s*#.*$//')
161-
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
162-
163-
for remove_pattern in "${PATTERNS_TO_REMOVE[@]}"; do
164-
if [[ "$pattern" == "$remove_pattern" ]]; then
165-
pattern_match=1
166-
break
167-
fi
282+
if ! $AUTO_CLEAN_GITIGNORE; then
283+
echo "The following patterns will be removed from .gitignore:"
284+
for pattern in "${PATTERNS_TO_REMOVE[@]}"; do
285+
echo " - $pattern"
168286
done
169287

170-
# Keep the line if pattern doesn't match any to remove
171-
if [[ $pattern_match -eq 0 ]]; then
172-
echo "$line" >> "$temp_file"
173-
else
174-
print_warning "Removed pattern: $pattern"
288+
if ! ask_yes_no "Proceed with removing these patterns?" "y"; then
289+
print_warning "No changes made to .gitignore file."
290+
PATTERNS_TO_REMOVE=()
175291
fi
176-
done < "$GITIGNORE_FILE"
292+
fi
177293

178-
cp "$temp_file" "$GITIGNORE_FILE"
179-
rm "$temp_file"
180-
print_success "Updated .gitignore file."
294+
if [[ ${#PATTERNS_TO_REMOVE[@]} -gt 0 ]]; then
295+
temp_file=$(mktemp)
296+
while IFS= read -r line || [[ -n "$line" ]]; do
297+
pattern_match=0
298+
299+
# Keep comments and empty lines
300+
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
301+
echo "$line" >> "$temp_file"
302+
continue
303+
fi
304+
305+
# Extract the pattern (ignore inline comments)
306+
pattern=$(echo "$line" | sed 's/\s*#.*$//')
307+
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
308+
309+
for remove_pattern in "${PATTERNS_TO_REMOVE[@]}"; do
310+
if [[ "$pattern" == "$remove_pattern" ]]; then
311+
pattern_match=1
312+
break
313+
fi
314+
done
315+
316+
# Keep the line if pattern doesn't match any to remove
317+
if [[ $pattern_match -eq 0 ]]; then
318+
echo "$line" >> "$temp_file"
319+
else
320+
print_warning "Removed pattern: $pattern"
321+
fi
322+
done < "$GITIGNORE_FILE"
323+
324+
cp "$temp_file" "$GITIGNORE_FILE"
325+
rm "$temp_file"
326+
print_success "Updated .gitignore file."
327+
fi
181328
fi
182329

183330
# Summary
184331
print_header "Summary"
185-
if [[ $TOTAL_MATCHED_FILES -eq 0 ]]; then
332+
if [[ ${#MATCHED_PATTERNS[@]} -eq 0 ]]; then
186333
print_success "No tracked files matching ignore patterns were found."
187334
echo "Your repository is in sync with your .gitignore file!"
188335
else

0 commit comments

Comments
 (0)