bimagic 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +23 -5
  2. package/bimagic +1206 -1153
  3. package/package.json +2 -4
package/bimagic CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- VERSION="v1.4.6"
3
+ VERSION="v1.5.0"
4
4
 
5
5
  if [[ "$1" == "--version" || "$1" == "-v" ]]; then
6
- echo "Bimagic Git Wizard $VERSION"
7
- exit 0
6
+ echo "Bimagic Git Wizard $VERSION"
7
+ exit 0
8
8
  fi
9
9
 
10
10
  # Configuration and Theme
@@ -29,20 +29,20 @@ BANNER_COLOR_5="135"
29
29
 
30
30
  # Load theme if it exists
31
31
  if [[ -f "$THEME_FILE" ]]; then
32
- source "$THEME_FILE"
32
+ source "$THEME_FILE"
33
33
  fi
34
34
 
35
35
  # Function to get ANSI escape sequence for echo
36
36
  get_ansi_esc() {
37
- local color=$1
38
- if [[ "$color" =~ ^# ]]; then
39
- local r=$(printf "%d" "0x${color:1:2}")
40
- local g=$(printf "%d" "0x${color:3:2}")
41
- local b=$(printf "%d" "0x${color:5:2}")
42
- echo -e "\033[38;2;${r};${g};${b}m"
43
- else
44
- echo -e "\033[38;5;${color}m"
45
- fi
37
+ local color=$1
38
+ if [[ "$color" =~ ^# ]]; then
39
+ local r=$(printf "%d" "0x${color:1:2}")
40
+ local g=$(printf "%d" "0x${color:3:2}")
41
+ local b=$(printf "%d" "0x${color:5:2}")
42
+ echo -e "\033[38;2;${r};${g};${b}m"
43
+ else
44
+ echo -e "\033[38;5;${color}m"
45
+ fi
46
46
  }
47
47
 
48
48
  # Colors for output (using theme)
@@ -57,25 +57,25 @@ NC=$(echo -e "\033[0m") # No Color
57
57
 
58
58
  # Function to draw a progress bar
59
59
  draw_progress_bar() {
60
- local label=$1
61
- local percent=$2
62
- local width=30
63
- local filled=$((percent * width / 100))
64
- local empty=$((width - filled))
65
-
66
- local bar=""
67
- for ((i = 0; i < filled; i++)); do bar+="█"; done
68
- local bg=""
69
- for ((i = 0; i < empty; i++)); do bg+="░"; done
70
-
71
- printf "\r\e[K${CYAN}%-20s${NC} [${PURPLE}%s${NC}${GRAY}%s${NC}] ${YELLOW}%3d%%${NC}" "$label" "$bar" "$bg" "$percent"
60
+ local label=$1
61
+ local percent=$2
62
+ local width=30
63
+ local filled=$((percent * width / 100))
64
+ local empty=$((width - filled))
65
+
66
+ local bar=""
67
+ for ((i = 0; i < filled; i++)); do bar+="█"; done
68
+ local bg=""
69
+ for ((i = 0; i < empty; i++)); do bg+="░"; done
70
+
71
+ printf "\r\e[K${CYAN}%-20s${NC} [${PURPLE}%s${NC}${GRAY}%s${NC}] ${YELLOW}%3d%%${NC}" "$label" "$bar" "$bg" "$percent"
72
72
  }
73
73
 
74
74
  # Ensure gum is installed
75
75
  if ! command -v gum &>/dev/null; then
76
- echo "Error: gum is not installed."
77
- echo "Please install it: https://github.com/charmbracelet/gum"
78
- exit 1
76
+ echo "Error: gum is not installed."
77
+ echo "Please install it: https://github.com/charmbracelet/gum"
78
+ exit 1
79
79
  fi
80
80
 
81
81
  echo "Welcome to the Git Wizard! Let's work some magic..."
@@ -83,254 +83,254 @@ echo
83
83
 
84
84
  # Sound functions
85
85
  play_sound() {
86
- local sound_type=$1
87
- case "$sound_type" in
88
- "success")
89
- # Pleasant bell sound
90
- echo -e "\a" # System bell
91
- # Alternative: play a beep sequence
92
- for i in {1..2}; do
93
- printf "\a"
94
- sleep 0.1
95
- done
96
- ;;
97
- "error")
98
- # Harsher error sound
99
- for i in {1..3}; do
100
- printf "\a"
101
- sleep 0.05
102
- done
103
- ;;
104
- "warning")
105
- # Single warning beep
106
- printf "\a"
107
- ;;
108
- "magic")
109
- # Magical sequence for special operations
110
- for i in {1..3}; do
111
- printf "\a"
112
- sleep 0.2
113
- done
114
- ;;
115
- "progress")
116
- # Subtle progress indicator
117
- printf "\a"
118
- ;;
119
- esac
86
+ local sound_type=$1
87
+ case "$sound_type" in
88
+ "success")
89
+ # Pleasant bell sound
90
+ echo -e "\a" # System bell
91
+ # Alternative: play a beep sequence
92
+ for i in {1..2}; do
93
+ printf "\a"
94
+ sleep 0.1
95
+ done
96
+ ;;
97
+ "error")
98
+ # Harsher error sound
99
+ for i in {1..3}; do
100
+ printf "\a"
101
+ sleep 0.05
102
+ done
103
+ ;;
104
+ "warning")
105
+ # Single warning beep
106
+ printf "\a"
107
+ ;;
108
+ "magic")
109
+ # Magical sequence for special operations
110
+ for i in {1..3}; do
111
+ printf "\a"
112
+ sleep 0.2
113
+ done
114
+ ;;
115
+ "progress")
116
+ # Subtle progress indicator
117
+ printf "\a"
118
+ ;;
119
+ esac
120
120
  }
121
121
  print_status() {
122
- gum style --foreground "$BIMAGIC_PRIMARY" "$1"
123
- play_sound "success"
122
+ gum style --foreground "$BIMAGIC_PRIMARY" "$1"
123
+ play_sound "success"
124
124
  }
125
125
 
126
126
  print_error() {
127
- gum style --foreground "$BIMAGIC_ERROR" "$1"
128
- play_sound "error"
127
+ gum style --foreground "$BIMAGIC_ERROR" "$1"
128
+ play_sound "error"
129
129
  }
130
130
 
131
131
  print_warning() {
132
- gum style --foreground "$BIMAGIC_WARNING" "$1"
133
- play_sound "warning"
132
+ gum style --foreground "$BIMAGIC_WARNING" "$1"
133
+ play_sound "warning"
134
134
  }
135
135
 
136
136
  # Function to get current branch with color
137
137
  get_current_branch() {
138
- git branch --show-current 2>/dev/null || echo "main"
138
+ git branch --show-current 2>/dev/null || echo "main"
139
139
  }
140
140
 
141
141
  # Function to display branches with gum
142
142
  show_branches() {
143
- local current_branch=$(get_current_branch)
144
-
145
- # Get all branches and highlight current one
146
- git branch -a --format='%(refname:short)' |
147
- while read -r branch; do
148
- if [[ "$branch" == "$current_branch" ]]; then
149
- echo -e "${GREEN}➤ $branch${NC} (current)"
150
- else
151
- echo " $branch"
152
- fi
153
- done | sort -u
143
+ local current_branch=$(get_current_branch)
144
+
145
+ # Get all branches and highlight current one
146
+ git branch -a --format='%(refname:short)' |
147
+ while read -r branch; do
148
+ if [[ "$branch" == "$current_branch" ]]; then
149
+ echo -e "${GREEN}➤ $branch${NC} (current)"
150
+ else
151
+ echo " $branch"
152
+ fi
153
+ done | sort -u
154
154
  }
155
155
 
156
156
  # Function to setup or update a remote
157
157
  setup_remote() {
158
- local remote_name=${1:-"origin"}
158
+ local remote_name=${1:-"origin"}
159
159
 
160
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
161
- print_error "Not a git repository! Initialize it first."
162
- return 1
163
- fi
160
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
161
+ print_error "Not a git repository! Initialize it first."
162
+ return 1
163
+ fi
164
164
 
165
- # Ask for protocol
166
- local protocol=$(gum choose --header "Select protocol for '$remote_name':" "HTTPS (Token)" "SSH")
165
+ # Ask for protocol
166
+ local protocol=$(gum choose --header "Select protocol for '$remote_name':" "HTTPS (Token)" "SSH")
167
167
 
168
- local remote_url=""
169
- if [[ "$protocol" == "HTTPS (Token)" ]]; then
170
- if [[ -z "$GITHUB_USER" ]] || [[ -z "$GITHUB_TOKEN" ]]; then
171
- print_error "GITHUB_USER and GITHUB_TOKEN required for HTTPS!"
172
- return 1
173
- fi
174
- local reponame=$(gum input --placeholder "Enter repo name (example: my-repo)")
175
- [[ -z "$reponame" ]] && return 1
176
- # Ensure it ends with .git consistently
177
- reponame="${reponame%.git}.git"
178
- remote_url="https://${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${reponame}"
179
- elif [[ "$protocol" == "SSH" ]]; then
180
- remote_url=$(gum input --placeholder "Enter SSH URL (e.g., git@github.com:user/repo.git)")
181
- [[ -z "$remote_url" ]] && return 1
182
- else
183
- print_warning "No protocol selected."
184
- return 1
185
- fi
186
-
187
- if gum confirm "Set remote '$remote_name' to $remote_url?"; then
188
- git remote remove "$remote_name" 2>/dev/null
189
- git remote add "$remote_name" "$remote_url"
190
- print_status " Remote '$remote_name' set to $remote_url"
191
- return 0
192
- else
193
- print_status "Operation cancelled."
194
- return 1
168
+ local remote_url=""
169
+ if [[ "$protocol" == "HTTPS (Token)" ]]; then
170
+ if [[ -z "$GITHUB_USER" ]] || [[ -z "$GITHUB_TOKEN" ]]; then
171
+ print_error "GITHUB_USER and GITHUB_TOKEN required for HTTPS!"
172
+ return 1
195
173
  fi
174
+ local reponame=$(gum input --placeholder "Enter repo name (example: my-repo)")
175
+ [[ -z "$reponame" ]] && return 1
176
+ # Ensure it ends with .git consistently
177
+ reponame="${reponame%.git}.git"
178
+ remote_url="https://${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${reponame}"
179
+ elif [[ "$protocol" == "SSH" ]]; then
180
+ remote_url=$(gum input --placeholder "Enter SSH URL (e.g., git@github.com:user/repo.git)")
181
+ [[ -z "$remote_url" ]] && return 1
182
+ else
183
+ print_warning "No protocol selected."
184
+ return 1
185
+ fi
186
+
187
+ if gum confirm "Set remote '$remote_name' to $remote_url?"; then
188
+ git remote remove "$remote_name" 2>/dev/null
189
+ git remote add "$remote_name" "$remote_url"
190
+ print_status " Remote '$remote_name' set to $remote_url"
191
+ return 0
192
+ else
193
+ print_status "Operation cancelled."
194
+ return 1
195
+ fi
196
196
  }
197
197
 
198
198
  show_repo_status() {
199
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
200
- print_warning "Not inside a git repository!"
201
- return
202
- fi
203
-
204
- local branch=$(get_current_branch)
205
-
206
- # Ahead/behind info
207
- local upstream=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
208
- local ahead behind
209
- if [[ -n "$upstream" ]]; then
210
- ahead=$(git rev-list --count "$upstream"..HEAD 2>/dev/null)
211
- behind=$(git rev-list --count HEAD.."$upstream" 2>/dev/null)
212
- else
213
- ahead=0
214
- behind=0
215
- fi
216
-
217
- # Working tree status
218
- local status
219
- if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
220
- if git ls-files -u | grep -q .; then
221
- status="🔴 conflicts"
222
- color="$BIMAGIC_ERROR"
223
- else
224
- status="🟢 clean"
225
- color="$BIMAGIC_SUCCESS"
226
- fi
199
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
200
+ print_warning "Not inside a git repository!"
201
+ return
202
+ fi
203
+
204
+ local branch=$(get_current_branch)
205
+
206
+ # Ahead/behind info
207
+ local upstream=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
208
+ local ahead behind
209
+ if [[ -n "$upstream" ]]; then
210
+ ahead=$(git rev-list --count "$upstream"..HEAD 2>/dev/null)
211
+ behind=$(git rev-list --count HEAD.."$upstream" 2>/dev/null)
212
+ else
213
+ ahead=0
214
+ behind=0
215
+ fi
216
+
217
+ # Working tree status
218
+ local status
219
+ if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
220
+ if git ls-files -u | grep -q .; then
221
+ status="🔴 conflicts"
222
+ color="$BIMAGIC_ERROR"
227
223
  else
228
- status="🟡 uncommitted"
229
- color="$BIMAGIC_WARNING"
224
+ status="🟢 clean"
225
+ color="$BIMAGIC_SUCCESS"
230
226
  fi
227
+ else
228
+ status="🟡 uncommitted"
229
+ color="$BIMAGIC_WARNING"
230
+ fi
231
231
 
232
- local display_user=${GITHUB_USER:-"SSH/Local"}
233
- local content="GITHUB USER: $display_user
232
+ local display_user=${GITHUB_USER:-"SSH/Local"}
233
+ local content="GITHUB USER: $display_user
234
234
  BRANCH: $branch
235
235
  AHEAD: $ahead | BEHIND: $behind
236
236
  STATUS: $status"
237
237
 
238
- echo
239
- gum style \
240
- --border rounded \
241
- --margin "1 0" \
242
- --padding "1 2" \
243
- --border-foreground "$color" \
244
- "$content"
238
+ echo
239
+ gum style \
240
+ --border rounded \
241
+ --margin "1 0" \
242
+ --padding "1 2" \
243
+ --border-foreground "$color" \
244
+ "$content"
245
245
  }
246
246
 
247
247
  generate_bar() {
248
- local percentage=$1
249
- local width=20
250
-
251
- # Convert float to integer for bar calculation
252
- local int_percentage=${percentage%.*}
253
- # Safety check for integer conversion
254
- if [[ ! "$int_percentage" =~ ^[0-9]+$ ]]; then int_percentage=0; fi
255
- [[ $int_percentage -gt 100 ]] && int_percentage=100
256
-
257
- local filled=$((int_percentage * width / 100))
258
- local empty=$((width - filled))
259
-
260
- # Pre-get theme colors for the gradient
261
- local c1=$(get_ansi_esc "$BANNER_COLOR_1")
262
- local c2=$(get_ansi_esc "$BANNER_COLOR_2")
263
- local c3=$(get_ansi_esc "$BANNER_COLOR_3")
264
- local c4=$(get_ansi_esc "$BANNER_COLOR_4")
265
- local c5=$(get_ansi_esc "$BANNER_COLOR_5")
266
- local colors=("$c1" "$c2" "$c3" "$c4" "$c5")
267
- local gray=$(get_ansi_esc "$BIMAGIC_MUTED")
268
-
269
- local bar="${gray}[${NC}"
270
- for ((i = 0; i < filled; i++)); do
271
- local color_idx=$(( i * 5 / width ))
272
- bar+="${colors[$color_idx]}█"
273
- done
274
-
275
- bar+="$gray"
276
- for ((i = 0; i < empty; i++)); do
277
- bar+="░"
278
- done
279
- bar+="]${NC}"
280
-
281
- echo -e "$bar"
248
+ local percentage=$1
249
+ local width=20
250
+
251
+ # Convert float to integer for bar calculation
252
+ local int_percentage=${percentage%.*}
253
+ # Safety check for integer conversion
254
+ if [[ ! "$int_percentage" =~ ^[0-9]+$ ]]; then int_percentage=0; fi
255
+ [[ $int_percentage -gt 100 ]] && int_percentage=100
256
+
257
+ local filled=$((int_percentage * width / 100))
258
+ local empty=$((width - filled))
259
+
260
+ # Pre-get theme colors for the gradient
261
+ local c1=$(get_ansi_esc "$BANNER_COLOR_1")
262
+ local c2=$(get_ansi_esc "$BANNER_COLOR_2")
263
+ local c3=$(get_ansi_esc "$BANNER_COLOR_3")
264
+ local c4=$(get_ansi_esc "$BANNER_COLOR_4")
265
+ local c5=$(get_ansi_esc "$BANNER_COLOR_5")
266
+ local colors=("$c1" "$c2" "$c3" "$c4" "$c5")
267
+ local gray=$(get_ansi_esc "$BIMAGIC_MUTED")
268
+
269
+ local bar="${gray}[${NC}"
270
+ for ((i = 0; i < filled; i++)); do
271
+ local color_idx=$((i * 5 / width))
272
+ bar+="${colors[$color_idx]}█"
273
+ done
274
+
275
+ bar+="$gray"
276
+ for ((i = 0; i < empty; i++)); do
277
+ bar+="░"
278
+ done
279
+ bar+="]${NC}"
280
+
281
+ echo -e "$bar"
282
282
  }
283
283
 
284
284
  # Function to get contributor statistics
285
285
  show_contributor_stats() {
286
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
287
- print_error "Not a git repository!"
288
- return 1
289
- fi
290
-
291
- # Time range selection
292
- local time_range=$(gum choose --header "Select time range" \
293
- "Last 7 days" \
294
- "Last 30 days" \
295
- "Last 90 days" \
296
- "Last year" \
297
- "All time")
298
-
299
- local since=""
300
- case "$time_range" in
301
- "Last 7 days") since="--since='7 days ago'" ;;
302
- "Last 30 days") since="--since='30 days ago'" ;;
303
- "Last 90 days") since="--since='3 months ago'" ;;
304
- "Last year") since="--since='1 year ago'" ;;
305
- "All time") since="" ;;
306
- *)
307
- print_warning "No time range selected."
308
- return 1
309
- ;;
310
- esac
311
-
312
- print_status "Analyzing contributions ($time_range)..."
313
- echo
314
-
315
- # Temporary file for processing
316
- local temp_file=$(mktemp)
317
-
318
- # Get git log with numstat and format for processing
319
- if [[ -n "$since" ]]; then
320
- gum spin --title "Analyzing log..." -- bash -c "git log --pretty=format:'COMMIT|%aN' --numstat $since > $temp_file 2>/dev/null"
321
- else
322
- gum spin --title "Analyzing log..." -- bash -c "git log --pretty=format:'COMMIT|%aN' --numstat > $temp_file 2>/dev/null"
323
- fi
324
-
325
- # Check if we got any data
326
- if [[ ! -s "$temp_file" ]]; then
327
- print_error "No contribution data found for the selected period."
328
- rm -f "$temp_file"
329
- return 1
330
- fi
286
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
287
+ print_error "Not a git repository!"
288
+ return 1
289
+ fi
290
+
291
+ # Time range selection
292
+ local time_range=$(gum choose --header "Select time range" \
293
+ "Last 7 days" \
294
+ "Last 30 days" \
295
+ "Last 90 days" \
296
+ "Last year" \
297
+ "All time")
298
+
299
+ local since=""
300
+ case "$time_range" in
301
+ "Last 7 days") since="--since='7 days ago'" ;;
302
+ "Last 30 days") since="--since='30 days ago'" ;;
303
+ "Last 90 days") since="--since='3 months ago'" ;;
304
+ "Last year") since="--since='1 year ago'" ;;
305
+ "All time") since="" ;;
306
+ *)
307
+ print_warning "No time range selected."
308
+ return 1
309
+ ;;
310
+ esac
311
+
312
+ print_status "Analyzing contributions ($time_range)..."
313
+ echo
314
+
315
+ # Temporary file for processing
316
+ local temp_file=$(mktemp)
317
+
318
+ # Get git log with numstat and format for processing
319
+ if [[ -n "$since" ]]; then
320
+ gum spin --title "Analyzing log..." -- bash -c "git log --pretty=format:'COMMIT|%aN' --numstat $since > $temp_file 2>/dev/null"
321
+ else
322
+ gum spin --title "Analyzing log..." -- bash -c "git log --pretty=format:'COMMIT|%aN' --numstat > $temp_file 2>/dev/null"
323
+ fi
324
+
325
+ # Check if we got any data
326
+ if [[ ! -s "$temp_file" ]]; then
327
+ print_error "No contribution data found for the selected period."
328
+ rm -f "$temp_file"
329
+ return 1
330
+ fi
331
331
 
332
- # Process the data with awk
333
- local stats=$(awk '
332
+ # Process the data with awk
333
+ local stats=$(awk '
334
334
  BEGIN {
335
335
  total_lines = 0
336
336
  }
@@ -365,314 +365,314 @@ show_contributor_stats() {
365
365
  }
366
366
  }' "$temp_file" | sort -t'|' -k2 -nr)
367
367
 
368
- rm -f "$temp_file"
368
+ rm -f "$temp_file"
369
369
 
370
- if [[ -z "$stats" ]]; then
371
- print_error "No contribution data found for the selected period."
372
- return 1
373
- fi
370
+ if [[ -z "$stats" ]]; then
371
+ print_error "No contribution data found for the selected period."
372
+ return 1
373
+ fi
374
374
 
375
- # Calculate additional metrics
376
- local most_active_author=""
377
- local most_commits=0
378
- local most_productive_author=""
379
- local highest_avg=0
375
+ # Calculate additional metrics
376
+ local most_active_author=""
377
+ local most_commits=0
378
+ local most_productive_author=""
379
+ local highest_avg=0
380
380
 
381
- # Display header
382
- echo -e "${PURPLE}Contribution Report ($time_range)${NC}"
383
- echo "$(printf '─%.0s' $(seq 1 45))"
381
+ # Display header
382
+ echo -e "${PURPLE}Contribution Report ($time_range)${NC}"
383
+ echo "$(printf '─%.0s' $(seq 1 45))"
384
384
 
385
- # Display each contributor
386
- while IFS='|' read -r author lines commits percentage; do
387
- # Clean up author name
388
- author=$(echo "$author" | sed 's/^ *//;s/ *$//')
385
+ # Display each contributor
386
+ while IFS='|' read -r author lines commits percentage; do
387
+ # Clean up author name
388
+ author=$(echo "$author" | sed 's/^ *//;s/ *$//')
389
389
 
390
- # Generate themed bar
391
- local bar=$(generate_bar "$percentage")
390
+ # Generate themed bar
391
+ local bar=$(generate_bar "$percentage")
392
392
 
393
- # Display entry with theme colors
394
- printf "${BLUE}%-15s${NC} %s ${YELLOW}%5.1f%%${NC} (${CYAN}%d lines${NC})\n" \
395
- "$author" "$bar" "$percentage" "$lines"
393
+ # Display entry with theme colors
394
+ printf "${BLUE}%-15s${NC} %s ${YELLOW}%5.1f%%${NC} (${CYAN}%d lines${NC})\n" \
395
+ "$author" "$bar" "$percentage" "$lines"
396
396
 
397
- # Track most active (most commits)
398
- if [[ $commits -gt $most_commits ]]; then
399
- most_commits=$commits
400
- most_active_author="$author"
401
- fi
397
+ # Track most active (most commits)
398
+ if [[ $commits -gt $most_commits ]]; then
399
+ most_commits=$commits
400
+ most_active_author="$author"
401
+ fi
402
402
 
403
- # Track most productive (highest average lines per commit)
404
- if [[ $commits -gt 0 ]]; then
405
- local avg_lines=$((lines / commits))
406
- if [[ $avg_lines -gt $highest_avg ]]; then
407
- highest_avg=$avg_lines
408
- most_productive_author="$author"
409
- fi
410
- fi
403
+ # Track most productive (highest average lines per commit)
404
+ if [[ $commits -gt 0 ]]; then
405
+ local avg_lines=$((lines / commits))
406
+ if [[ $avg_lines -gt $highest_avg ]]; then
407
+ highest_avg=$avg_lines
408
+ most_productive_author="$author"
409
+ fi
410
+ fi
411
411
 
412
- done <<<"$stats"
412
+ done <<<"$stats"
413
413
 
414
- echo
415
- echo -e "${CYAN}Highlights:${NC}"
416
- if [[ -n "$most_active_author" ]]; then
417
- echo -e "${BLUE}Most Active:${NC} ${PURPLE}$most_active_author${NC} (${YELLOW}$most_commits commits${NC})"
418
- fi
419
- if [[ -n "$most_productive_author" ]]; then
420
- echo -e "${BLUE}Most Productive:${NC} ${PURPLE}$most_productive_author${NC} (${YELLOW}$highest_avg lines/commit${NC})"
421
- fi
414
+ echo
415
+ echo -e "${CYAN}Highlights:${NC}"
416
+ if [[ -n "$most_active_author" ]]; then
417
+ echo -e "${BLUE}Most Active:${NC} ${PURPLE}$most_active_author${NC} (${YELLOW}$most_commits commits${NC})"
418
+ fi
419
+ if [[ -n "$most_productive_author" ]]; then
420
+ echo -e "${BLUE}Most Productive:${NC} ${PURPLE}$most_productive_author${NC} (${YELLOW}$highest_avg lines/commit${NC})"
421
+ fi
422
422
 
423
- local total_contributors=$(echo "$stats" | wc -l)
424
- echo -e "${BLUE}Total Contributors:${NC} ${YELLOW}$total_contributors${NC}"
423
+ local total_contributors=$(echo "$stats" | wc -l)
424
+ echo -e "${BLUE}Total Contributors:${NC} ${YELLOW}$total_contributors${NC}"
425
425
  }
426
426
 
427
427
  # Function to clone a repository (Standard or Interactive)
428
428
  clone_repo() {
429
- local url=$1
430
- local interactive=$2
431
- local depth=$3
432
-
433
- # Extract repo name from URL (basename, remove .git)
434
- local repo_name=$(basename "$url" .git)
435
-
436
- if [[ -d "$repo_name" ]]; then
437
- print_error "Directory '$repo_name' already exists."
438
- return 1
439
- fi
429
+ local url=$1
430
+ local interactive=$2
431
+ local depth=$3
432
+
433
+ # Extract repo name from URL (basename, remove .git)
434
+ local repo_name=$(basename "$url" .git)
435
+
436
+ if [[ -d "$repo_name" ]]; then
437
+ print_error "Directory '$repo_name' already exists."
438
+ return 1
439
+ fi
440
+
441
+ local depth_arg=""
442
+ if [[ -n "$depth" ]]; then
443
+ depth_arg="--depth $depth"
444
+ fi
445
+
446
+ if [[ "$interactive" == "true" ]]; then
447
+ print_status "Initializing interactive clone for $repo_name..."
448
+
449
+ # 1. Clone with --no-checkout and --filter=blob:none (downloads commits/trees, no file contents)
450
+ print_status "Cloning structure for $repo_name..."
451
+ (
452
+ set -o pipefail
453
+ git clone --progress --filter=blob:none --no-checkout $depth_arg "$url" "$repo_name" 2>&1 | while read -r -d $'\r' line; do
454
+ if [[ $line =~ Receiving\ objects:\ +([0-9]+)% ]]; then
455
+ draw_progress_bar "Receiving Structure" "${BASH_REMATCH[1]}"
456
+ elif [[ $line =~ Resolving\ deltas:\ +([0-9]+)% ]]; then
457
+ draw_progress_bar "Resolving Deltas" "${BASH_REMATCH[1]}"
458
+ fi
459
+ done
460
+ )
461
+ local status=$?
462
+ echo ""
440
463
 
441
- local depth_arg=""
442
- if [[ -n "$depth" ]]; then
443
- depth_arg="--depth $depth"
464
+ if [[ $status -ne 0 || ! -d "$repo_name" ]]; then
465
+ print_error "Clone failed."
466
+ return 1
444
467
  fi
445
468
 
446
- if [[ "$interactive" == "true" ]]; then
447
- print_status "Initializing interactive clone for $repo_name..."
448
-
449
- # 1. Clone with --no-checkout and --filter=blob:none (downloads commits/trees, no file contents)
450
- print_status "Cloning structure for $repo_name..."
451
- (
452
- set -o pipefail
453
- git clone --progress --filter=blob:none --no-checkout $depth_arg "$url" "$repo_name" 2>&1 | while read -r -d $'\r' line; do
454
- if [[ $line =~ Receiving\ objects:\ +([0-9]+)% ]]; then
455
- draw_progress_bar "Receiving Structure" "${BASH_REMATCH[1]}"
456
- elif [[ $line =~ Resolving\ deltas:\ +([0-9]+)% ]]; then
457
- draw_progress_bar "Resolving Deltas" "${BASH_REMATCH[1]}"
458
- fi
459
- done
460
- )
461
- local status=$?
462
- echo ""
463
-
464
- if [[ $status -ne 0 || ! -d "$repo_name" ]]; then
465
- print_error "Clone failed."
466
- return 1
467
- fi
469
+ pushd "$repo_name" >/dev/null || return 1
468
470
 
469
- pushd "$repo_name" >/dev/null || return 1
471
+ # 2. List all files (from HEAD) and let user select
472
+ print_status "Fetching file list..."
473
+ local selected_paths
474
+ # git ls-tree -r --name-only HEAD lists all files recursively
475
+ selected_paths=$(git ls-tree -r --name-only HEAD | gum filter --no-limit --placeholder "Select files/folders to download (Space to select)")
470
476
 
471
- # 2. List all files (from HEAD) and let user select
472
- print_status "Fetching file list..."
473
- local selected_paths
474
- # git ls-tree -r --name-only HEAD lists all files recursively
475
- selected_paths=$(git ls-tree -r --name-only HEAD | gum filter --no-limit --placeholder "Select files/folders to download (Space to select)")
477
+ if [[ -z "$selected_paths" ]]; then
478
+ print_warning "No files selected. Aborting checkout."
479
+ popd >/dev/null
480
+ rm -rf "$repo_name"
481
+ return 1
482
+ fi
476
483
 
477
- if [[ -z "$selected_paths" ]]; then
478
- print_warning "No files selected. Aborting checkout."
479
- popd >/dev/null
480
- rm -rf "$repo_name"
481
- return 1
484
+ # 3. Configure sparse-checkout
485
+ # FIX: Add --no-cone so Git accepts individual file paths
486
+ gum spin --title "Configuring sparse checkout..." -- git sparse-checkout init --no-cone
487
+
488
+ # We use 'set' with the selected paths.
489
+ # Note: --cone mode works best with directories, but for specific files we might rely on the fact
490
+ # that sparse-checkout patterns handle full paths.
491
+ # However, 'git sparse-checkout set' treats arguments as patterns.
492
+ # We turn off cone for precise file selection transparency if needed, but standard 'set' often works.
493
+ # To be safe for exact file paths, we simply pass them.
494
+ # If the list is huge, this might fail on command line length.
495
+ # Ideally we pipe to 'git sparse-checkout set --stdin', but gum returns newline separated.
496
+
497
+ print_status "Downloading selected files..."
498
+ (
499
+ set -o pipefail
500
+ git checkout --progress HEAD 2>&1 | while read -r -d $'\r' line; do
501
+ if [[ $line =~ Updating\ files:\ +([0-9]+)% ]]; then
502
+ draw_progress_bar "Updating Files" "${BASH_REMATCH[1]}"
482
503
  fi
504
+ done
505
+ )
506
+ echo ""
507
+
508
+ popd >/dev/null
509
+ print_status "Successfully cloned selected files into '$repo_name'!"
510
+
511
+ else
512
+ # Standard clone with progress bar
513
+ print_status "Cloning $url into $repo_name..."
514
+
515
+ # Use a subshell with pipefail to catch git clone errors
516
+ (
517
+ set -o pipefail
518
+ git clone --progress $depth_arg "$url" "$repo_name" 2>&1 | while read -r -d $'\r' line; do
519
+ if [[ $line =~ Receiving\ objects:\ +([0-9]+)% ]]; then
520
+ draw_progress_bar "Receiving Objects" "${BASH_REMATCH[1]}"
521
+ elif [[ $line =~ Resolving\ deltas:\ +([0-9]+)% ]]; then
522
+ draw_progress_bar "Resolving Deltas" "${BASH_REMATCH[1]}"
523
+ fi
524
+ done
525
+ )
526
+ local status=$?
527
+ echo "" # New line after progress bar
483
528
 
484
- # 3. Configure sparse-checkout
485
- # FIX: Add --no-cone so Git accepts individual file paths
486
- gum spin --title "Configuring sparse checkout..." -- git sparse-checkout init --no-cone
487
-
488
- # We use 'set' with the selected paths.
489
- # Note: --cone mode works best with directories, but for specific files we might rely on the fact
490
- # that sparse-checkout patterns handle full paths.
491
- # However, 'git sparse-checkout set' treats arguments as patterns.
492
- # We turn off cone for precise file selection transparency if needed, but standard 'set' often works.
493
- # To be safe for exact file paths, we simply pass them.
494
- # If the list is huge, this might fail on command line length.
495
- # Ideally we pipe to 'git sparse-checkout set --stdin', but gum returns newline separated.
496
-
497
- print_status "Downloading selected files..."
498
- (
499
- set -o pipefail
500
- git checkout --progress HEAD 2>&1 | while read -r -d $'\r' line; do
501
- if [[ $line =~ Updating\ files:\ +([0-9]+)% ]]; then
502
- draw_progress_bar "Updating Files" "${BASH_REMATCH[1]}"
503
- fi
504
- done
505
- )
506
- echo ""
507
-
508
- popd >/dev/null
509
- print_status "Successfully cloned selected files into '$repo_name'!"
510
-
529
+ if [[ $status -eq 0 && -d "$repo_name" ]]; then
530
+ print_status "Successfully cloned '$url' into '$repo_name'!"
511
531
  else
512
- # Standard clone with progress bar
513
- print_status "Cloning $url into $repo_name..."
514
-
515
- # Use a subshell with pipefail to catch git clone errors
516
- (
517
- set -o pipefail
518
- git clone --progress $depth_arg "$url" "$repo_name" 2>&1 | while read -r -d $'\r' line; do
519
- if [[ $line =~ Receiving\ objects:\ +([0-9]+)% ]]; then
520
- draw_progress_bar "Receiving Objects" "${BASH_REMATCH[1]}"
521
- elif [[ $line =~ Resolving\ deltas:\ +([0-9]+)% ]]; then
522
- draw_progress_bar "Resolving Deltas" "${BASH_REMATCH[1]}"
523
- fi
524
- done
525
- )
526
- local status=$?
527
- echo "" # New line after progress bar
528
-
529
- if [[ $status -eq 0 && -d "$repo_name" ]]; then
530
- print_status "Successfully cloned '$url' into '$repo_name'!"
531
- else
532
- print_error "Clone failed."
533
- return 1
534
- fi
532
+ print_error "Clone failed."
533
+ return 1
535
534
  fi
535
+ fi
536
536
  }
537
537
 
538
538
  # a .gitignore generator to your tool
539
539
  summon_gitignore() {
540
- # Check for curl
541
- if ! command -v curl &>/dev/null; then
542
- print_error "Error: curl is not installed. Required to summon the Architect."
543
- return 1
544
- fi
545
-
546
- print_status "📜 Summoning the Architect..."
547
-
548
- # Check if .gitignore already exists
549
- if [[ -f ".gitignore" ]]; then
550
- print_warning "A .gitignore file already exists in this directory."
551
- if ! gum confirm "Do you want to overwrite it?"; then
552
- print_status "Operation cancelled."
553
- return 0
554
- fi
540
+ # Check for curl
541
+ if ! command -v curl &>/dev/null; then
542
+ print_error "Error: curl is not installed. Required to summon the Architect."
543
+ return 1
544
+ fi
545
+
546
+ print_status "📜 Summoning the Architect..."
547
+
548
+ # Check if .gitignore already exists
549
+ if [[ -f ".gitignore" ]]; then
550
+ print_warning "A .gitignore file already exists in this directory."
551
+ if ! gum confirm "Do you want to overwrite it?"; then
552
+ print_status "Operation cancelled."
553
+ return 0
555
554
  fi
556
-
557
- # List of popular templates (curated from github/gitignore)
558
- local templates=(
559
- "Actionscript" "Ada" "Android" "Angular" "AppEngine" "ArchLinuxPackages" "Autotools"
560
- "C++" "C" "CMake" "CUDA" "CakePHP" "ChefCookbook" "Clojure" "CodeIgniter" "Composer"
561
- "Dart" "Delphi" "Dotnet" "Drupal" "Elixir" "Elm" "Erlang" "Flutter" "Fortran"
562
- "Go" "Godot" "Gradle" "Grails" "Haskell" "Haxe" "Java" "Jekyll" "Joomla" "Julia"
563
- "Kotlin" "Laravel" "Lua" "Magento" "Maven" "Nextjs" "Nim" "Nix" "Node" "Objective-C"
564
- "Opa" "Perl" "Phalcon" "PlayFramework" "Prestashop" "Processing" "Python" "Qt"
565
- "R" "ROS" "Rails" "Ruby" "Rust" "Scala" "Scheme" "Smalltalk" "Swift" "Symfony"
566
- "Terraform" "TeX" "Unity" "UnrealEngine" "VisualStudio" "WordPress" "Zig"
567
- )
568
-
569
- # Use gum filter for a better selection experience
570
- local template=$(printf "%s\n" "${templates[@]}" | gum filter --placeholder "Search for a blueprint (e.g., Python, Node, Rust)...")
571
-
572
- if [[ -z "$template" ]]; then
573
- print_status "Cancelled."
574
- return 0
575
- fi
576
-
577
- # Fetch the template directly from GitHub's official repository
578
- print_status "Drawing the magic circle for $template..."
579
-
580
- local url="https://raw.githubusercontent.com/github/gitignore/main/${template}.gitignore"
581
-
582
- if gum spin --title "Fetching template..." -- curl -sL "$url" -o .gitignore; then
583
- # Verify the file is not empty (curl might return 404 text if URL is wrong)
584
- if grep -q "404: Not Found" .gitignore; then
585
- print_error "Failed to summon template: 404 Not Found at $url"
586
- rm .gitignore
587
- return 1
588
- fi
589
- print_status "✨ .gitignore for $template created successfully!"
590
- else
591
- print_error "Failed to summon template. Check your internet connection."
592
- return 1
555
+ fi
556
+
557
+ # List of popular templates (curated from github/gitignore)
558
+ local templates=(
559
+ "Actionscript" "Ada" "Android" "Angular" "AppEngine" "ArchLinuxPackages" "Autotools"
560
+ "C++" "C" "CMake" "CUDA" "CakePHP" "ChefCookbook" "Clojure" "CodeIgniter" "Composer"
561
+ "Dart" "Delphi" "Dotnet" "Drupal" "Elixir" "Elm" "Erlang" "Flutter" "Fortran"
562
+ "Go" "Godot" "Gradle" "Grails" "Haskell" "Haxe" "Java" "Jekyll" "Joomla" "Julia"
563
+ "Kotlin" "Laravel" "Lua" "Magento" "Maven" "Nextjs" "Nim" "Nix" "Node" "Objective-C"
564
+ "Opa" "Perl" "Phalcon" "PlayFramework" "Prestashop" "Processing" "Python" "Qt"
565
+ "R" "ROS" "Rails" "Ruby" "Rust" "Scala" "Scheme" "Smalltalk" "Swift" "Symfony"
566
+ "Terraform" "TeX" "Unity" "UnrealEngine" "VisualStudio" "WordPress" "Zig"
567
+ )
568
+
569
+ # Use gum filter for a better selection experience
570
+ local template=$(printf "%s\n" "${templates[@]}" | gum filter --placeholder "Search for a blueprint (e.g., Python, Node, Rust)...")
571
+
572
+ if [[ -z "$template" ]]; then
573
+ print_status "Cancelled."
574
+ return 0
575
+ fi
576
+
577
+ # Fetch the template directly from GitHub's official repository
578
+ print_status "Drawing the magic circle for $template..."
579
+
580
+ local url="https://raw.githubusercontent.com/github/gitignore/main/${template}.gitignore"
581
+
582
+ if gum spin --title "Fetching template..." -- curl -sL "$url" -o .gitignore; then
583
+ # Verify the file is not empty (curl might return 404 text if URL is wrong)
584
+ if grep -q "404: Not Found" .gitignore; then
585
+ print_error "Failed to summon template: 404 Not Found at $url"
586
+ rm .gitignore
587
+ return 1
593
588
  fi
589
+ print_status "✨ .gitignore for $template created successfully!"
590
+ else
591
+ print_error "Failed to summon template. Check your internet connection."
592
+ return 1
593
+ fi
594
594
  }
595
595
 
596
596
  # Function for Conventional Commits
597
597
  commit_wizard() {
598
- echo -e "${PURPLE}=== The Alchemist's Commit ===${NC}"
599
-
600
- # 1. Select Type
601
- local type=$(gum choose --header "Select change type:" \
602
- "feat: A new feature" \
603
- "fix: A bug fix" \
604
- "docs: Documentation only changes" \
605
- "style: Changes that do not affect the meaning of the code" \
606
- "refactor: A code change that neither fixes a bug nor adds a feature" \
607
- "perf: A code change that improves performance" \
608
- "test: Adding missing tests or correcting existing tests" \
609
- "chore: Changes to the build process or auxiliary tools")
610
-
611
- # Extract just the type (e.g., "feat") from the selection
612
- type=$(echo "$type" | cut -d: -f1)
613
- [[ -z "$type" ]] && return 1
614
-
615
- # 2. Scope (Optional)
616
- local scope=$(gum input --placeholder "Scope (optional, e.g., 'login', 'ui'). Press Enter to skip.")
617
-
618
- # 3. Short Description
619
- local summary=$(gum input --placeholder "Short description (imperative mood, e.g., 'add generic login')")
620
- [[ -z "$summary" ]] && print_warning "Summary is required!" && return 1
621
-
622
- # 4. Long Description (Optional)
623
- local body=""
624
- if gum confirm "Add a longer description (body)?"; then
625
- body=$(gum write --placeholder "Enter detailed description...")
626
- fi
627
-
628
- # 5. Breaking Change?
629
- local breaking=""
630
- if gum confirm "Is this a BREAKING CHANGE?"; then
631
- breaking="!"
632
- fi
633
-
634
- # Construct the message
635
- local commit_msg=""
636
- if [[ -n "$scope" ]]; then
637
- commit_msg="${type}(${scope})${breaking}: ${summary}"
638
- else
639
- commit_msg="${type}${breaking}: ${summary}"
640
- fi
641
-
642
- # Append body if exists
643
- if [[ -n "$body" ]]; then
644
- commit_msg="${commit_msg}
598
+ echo -e "${PURPLE}=== The Alchemist's Commit ===${NC}"
599
+
600
+ # 1. Select Type
601
+ local type=$(gum choose --header "Select change type:" \
602
+ "feat: A new feature" \
603
+ "fix: A bug fix" \
604
+ "docs: Documentation only changes" \
605
+ "style: Changes that do not affect the meaning of the code" \
606
+ "refactor: A code change that neither fixes a bug nor adds a feature" \
607
+ "perf: A code change that improves performance" \
608
+ "test: Adding missing tests or correcting existing tests" \
609
+ "chore: Changes to the build process or auxiliary tools")
610
+
611
+ # Extract just the type (e.g., "feat") from the selection
612
+ type=$(echo "$type" | cut -d: -f1)
613
+ [[ -z "$type" ]] && return 1
614
+
615
+ # 2. Scope (Optional)
616
+ local scope=$(gum input --placeholder "Scope (optional, e.g., 'login', 'ui'). Press Enter to skip.")
617
+
618
+ # 3. Short Description
619
+ local summary=$(gum input --placeholder "Short description (imperative mood, e.g., 'add generic login')")
620
+ [[ -z "$summary" ]] && print_warning "Summary is required!" && return 1
621
+
622
+ # 4. Long Description (Optional)
623
+ local body=""
624
+ if gum confirm "Add a longer description (body)?"; then
625
+ body=$(gum write --placeholder "Enter detailed description...")
626
+ fi
627
+
628
+ # 5. Breaking Change?
629
+ local breaking=""
630
+ if gum confirm "Is this a BREAKING CHANGE?"; then
631
+ breaking="!"
632
+ fi
633
+
634
+ # Construct the message
635
+ local commit_msg=""
636
+ if [[ -n "$scope" ]]; then
637
+ commit_msg="${type}(${scope})${breaking}: ${summary}"
638
+ else
639
+ commit_msg="${type}${breaking}: ${summary}"
640
+ fi
641
+
642
+ # Append body if exists
643
+ if [[ -n "$body" ]]; then
644
+ commit_msg="${commit_msg}
645
645
 
646
646
  ${body}"
647
- fi
648
-
649
- # Preview and Confirm
650
- echo
651
- gum style --border rounded --border-foreground "$BIMAGIC_PRIMARY" --padding "1 2" \
652
- "PREVIEW:" "$commit_msg"
653
- echo
654
-
655
- if gum confirm "Commit with this message?"; then
656
- git commit -m "$commit_msg"
657
- print_status "󱝁 Mischief managed! (Commit successful)"
658
- else
659
- print_warning "Commit cancelled."
660
- fi
647
+ fi
648
+
649
+ # Preview and Confirm
650
+ echo
651
+ gum style --border rounded --border-foreground "$BIMAGIC_PRIMARY" --padding "1 2" \
652
+ "PREVIEW:" "$commit_msg"
653
+ echo
654
+
655
+ if gum confirm "Commit with this message?"; then
656
+ git commit -m "$commit_msg"
657
+ print_status "󱝁 Mischief managed! (Commit successful)"
658
+ else
659
+ print_warning "Commit cancelled."
660
+ fi
661
661
  }
662
662
 
663
663
  #function to display git graph
664
664
  pretty_git_log() {
665
- git log --graph \
666
- --abbrev-commit \
667
- --decorate \
668
- --date=short \
669
- --format="%C(auto)%h%Creset %C(blue)%ad%Creset %C(green)%an%Creset %C(yellow)%d%Creset %Creset%s" \
670
- --all
665
+ git log --graph \
666
+ --abbrev-commit \
667
+ --decorate \
668
+ --date=short \
669
+ --format="%C(auto)%h%Creset %C(blue)%ad%Creset %C(green)%an%Creset %C(yellow)%d%Creset %Creset%s" \
670
+ --all
671
671
  }
672
672
 
673
673
  # Check if environment variables are set (optional for SSH)
674
674
  if [[ -z "$GITHUB_USER" ]] || [[ -z "$GITHUB_TOKEN" ]]; then
675
- print_warning "GITHUB_USER or GITHUB_TOKEN not set. Defaulting to SSH/Local mode."
675
+ print_warning "GITHUB_USER or GITHUB_TOKEN not set. Defaulting to SSH/Local mode."
676
676
  fi
677
677
 
678
678
  # Parse CLI arguments
@@ -684,749 +684,802 @@ CLI_DEPTH=""
684
684
 
685
685
  # Preserve args in a loop
686
686
  while [[ $# -gt 0 ]]; do
687
- case "$1" in
688
- -d)
689
- CLI_MODE="clone"
690
- shift
691
- ;;
692
- --depth)
693
- CLI_DEPTH="$2"
694
- shift 2
695
- ;;
696
- -i)
697
- CLI_INTERACTIVE="true"
698
- shift
699
- ;;
700
- -z)
701
- CLI_MODE="lazy"
702
- shift
703
- ;;
704
- -s)
705
- CLI_MODE="status"
706
- shift
707
- ;;
708
- -u)
709
- CLI_MODE="undo"
710
- shift
711
- ;;
712
- -g)
713
- CLI_MODE="graph"
714
- shift
715
- ;;
716
- -p)
717
- CLI_MODE="pull"
718
- shift
719
- ;;
720
- -a | --architect)
721
- CLI_MODE="architect"
722
- shift
723
- ;;
724
- *)
725
- # Argument handling based on current mode
726
- if [[ "$CLI_MODE" == "clone" && -z "$CLI_URL" ]]; then
727
- CLI_URL="$1"
728
- elif [[ "$CLI_MODE" == "lazy" && -z "$CLI_MSG" ]]; then
729
- CLI_MSG="$1"
730
- fi
731
- shift
732
- ;;
733
- esac
687
+ case "$1" in
688
+ -d)
689
+ CLI_MODE="clone"
690
+ shift
691
+ ;;
692
+ --depth)
693
+ CLI_DEPTH="$2"
694
+ shift 2
695
+ ;;
696
+ -i)
697
+ CLI_INTERACTIVE="true"
698
+ shift
699
+ ;;
700
+ -z)
701
+ CLI_MODE="lazy"
702
+ shift
703
+ ;;
704
+ -s)
705
+ CLI_MODE="status"
706
+ shift
707
+ ;;
708
+ -u)
709
+ CLI_MODE="undo"
710
+ shift
711
+ ;;
712
+ -g)
713
+ CLI_MODE="graph"
714
+ shift
715
+ ;;
716
+ -p)
717
+ CLI_MODE="pull"
718
+ shift
719
+ ;;
720
+ -a | --architect)
721
+ CLI_MODE="architect"
722
+ shift
723
+ ;;
724
+ *)
725
+ # Argument handling based on current mode
726
+ if [[ "$CLI_MODE" == "clone" && -z "$CLI_URL" ]]; then
727
+ CLI_URL="$1"
728
+ elif [[ "$CLI_MODE" == "lazy" && -z "$CLI_MSG" ]]; then
729
+ CLI_MSG="$1"
730
+ fi
731
+ shift
732
+ ;;
733
+ esac
734
734
  done
735
735
 
736
736
  # --- Mode Handlers ---
737
737
 
738
738
  if [[ "$CLI_MODE" == "clone" ]]; then
739
- if [[ -z "$CLI_URL" ]]; then
740
- print_error "Error: Repository URL required with -d"
741
- exit 1
742
- fi
743
- clone_repo "$CLI_URL" "$CLI_INTERACTIVE" "$CLI_DEPTH"
744
- exit 0
739
+ if [[ -z "$CLI_URL" ]]; then
740
+ print_error "Error: Repository URL required with -d"
741
+ exit 1
742
+ fi
743
+ clone_repo "$CLI_URL" "$CLI_INTERACTIVE" "$CLI_DEPTH"
744
+ exit 0
745
745
  fi
746
746
 
747
-
748
747
  if [[ "$CLI_MODE" == "status" ]]; then
749
- show_repo_status
750
- exit 0
748
+ show_repo_status
749
+ exit 0
751
750
  fi
752
751
 
753
752
  if [[ "$CLI_MODE" == "pull" ]]; then
754
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
755
- print_error "Not a git repository!"
756
- exit 1
757
- fi
758
-
759
- # 1. Fetch
760
- if gum spin --title "Fetching updates..." -- git fetch --all; then
761
- print_status "Fetch complete."
762
- else
763
- print_warning "Fetch encountered issues during fetch."
764
- fi
765
-
766
- # 2. Pull
767
- if gum spin --title "Pulling all..." -- git pull --all; then
768
- print_status "Pull all complete."
769
- else
770
- print_error "Pull failed. There might be conflicts or no upstream set."
771
- exit 1
772
- fi
773
- exit 0
753
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
754
+ print_error "Not a git repository!"
755
+ exit 1
756
+ fi
757
+
758
+ # 1. Fetch
759
+ if gum spin --title "Fetching updates..." -- git fetch --all; then
760
+ print_status "Fetch complete."
761
+ else
762
+ print_warning "Fetch encountered issues during fetch."
763
+ fi
764
+
765
+ # 2. Pull
766
+ if gum spin --title "Pulling all..." -- git pull --all; then
767
+ print_status "Pull all complete."
768
+ else
769
+ print_error "Pull failed. There might be conflicts or no upstream set."
770
+ exit 1
771
+ fi
772
+ exit 0
774
773
  fi
775
774
 
776
775
  if [[ "$CLI_MODE" == "graph" ]]; then
777
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
778
- print_error "Not a git repository!"
779
- exit 1
780
- fi
781
- pretty_git_log
782
- exit 0
776
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
777
+ print_error "Not a git repository!"
778
+ exit 1
779
+ fi
780
+ pretty_git_log
781
+ exit 0
783
782
  fi
784
783
 
785
784
  if [[ "$CLI_MODE" == "architect" ]]; then
786
- summon_gitignore
787
- exit 0
785
+ summon_gitignore
786
+ exit 0
788
787
  fi
789
788
 
790
789
  # Mode: Time Turner (Undo)
791
790
  if [[ "$CLI_MODE" == "undo" ]]; then
792
- print_status "󱦟 Spinning the Time Turner..."
791
+ print_status "󱦟 Spinning the Time Turner..."
793
792
 
794
- # Check if there is a commit to undo
795
- if ! git rev-parse HEAD >/dev/null 2>&1; then
796
- print_error "No commits to undo! This repo is empty."
797
- exit 1
793
+ # Check if there is a commit to undo
794
+ if ! git rev-parse HEAD >/dev/null 2>&1; then
795
+ print_error "No commits to undo! This repo is empty."
796
+ exit 1
797
+ fi
798
+
799
+ # Check if we are on the initial commit (no parent)
800
+ is_initial_commit=false
801
+ if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
802
+ is_initial_commit=true
803
+ fi
804
+
805
+ # Ask user for the type of undo
806
+ undo_type=$(gum choose --header "Select Undo Level:" \
807
+ "Soft (Undo commit, keep changes staged - Best for fixing typos)" \
808
+ "Mixed (Undo commit, keep changes unstaged - Best for splitting work)" \
809
+ "Hard (DESTROY changes - Revert to previous state)" \
810
+ "Cancel")
811
+
812
+ case "$undo_type" in
813
+ "Soft"*)
814
+ if [[ "$is_initial_commit" == "true" ]]; then
815
+ git update-ref -d HEAD
816
+ else
817
+ git reset --soft HEAD~1
798
818
  fi
799
-
800
- # Check if we are on the initial commit (no parent)
801
- is_initial_commit=false
802
- if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
803
- is_initial_commit=true
819
+ print_status "✨ Success! I undid the commit, but kept your files ready to commit again."
820
+ ;;
821
+ "Mixed"*)
822
+ if [[ "$is_initial_commit" == "true" ]]; then
823
+ git update-ref -d HEAD
824
+ git rm --cached -r -q .
825
+ else
826
+ git reset HEAD~1
804
827
  fi
805
-
806
- # Ask user for the type of undo
807
- undo_type=$(gum choose --header "Select Undo Level:" \
808
- "Soft (Undo commit, keep changes staged - Best for fixing typos)" \
809
- "Mixed (Undo commit, keep changes unstaged - Best for splitting work)" \
810
- "Hard (DESTROY changes - Revert to previous state)" \
811
- "Cancel")
812
-
813
- case "$undo_type" in
814
- "Soft"*)
815
- if [[ "$is_initial_commit" == "true" ]]; then
816
- git update-ref -d HEAD
817
- else
818
- git reset --soft HEAD~1
819
- fi
820
- print_status "✨ Success! I undid the commit, but kept your files ready to commit again."
821
- ;;
822
- "Mixed"*)
823
- if [[ "$is_initial_commit" == "true" ]]; then
824
- git update-ref -d HEAD
825
- git rm --cached -r -q .
826
- else
827
- git reset HEAD~1
828
- fi
829
- print_status "󱞈 Success! I undid the commit and unstaged the files."
830
- ;;
831
- "Hard"*)
832
- if gum confirm " DANGER: This deletes your work forever. Are you sure?"; then
833
- if [[ "$is_initial_commit" == "true" ]]; then
834
- # Unstage everything first, then clean to ensure files are deleted
835
- git update-ref -d HEAD
836
- git rm --cached -r -q . >/dev/null 2>&1
837
- git clean -fd
838
- else
839
- git reset --hard HEAD~1
840
- fi
841
- print_status "󱠇 Obliviate! The last commit and its changes are destroyed."
842
- else
843
- print_status "Operation cancelled."
844
- fi
845
- ;;
846
- *)
847
- print_status "Mischief managed (Cancelled)."
848
- ;;
849
- esac
850
- exit 0
828
+ print_status "󱞈 Success! I undid the commit and unstaged the files."
829
+ ;;
830
+ "Hard"*)
831
+ if gum confirm " DANGER: This deletes your work forever. Are you sure?"; then
832
+ if [[ "$is_initial_commit" == "true" ]]; then
833
+ # Unstage everything first, then clean to ensure files are deleted
834
+ git update-ref -d HEAD
835
+ git rm --cached -r -q . >/dev/null 2>&1
836
+ git clean -fd
837
+ else
838
+ git reset --hard HEAD~1
839
+ fi
840
+ print_status "󱠇 Obliviate! The last commit and its changes are destroyed."
841
+ else
842
+ print_status "Operation cancelled."
843
+ fi
844
+ ;;
845
+ *)
846
+ print_status "Mischief managed (Cancelled)."
847
+ ;;
848
+ esac
849
+ exit 0
851
850
  fi
852
851
 
853
852
  if [[ "$CLI_MODE" == "lazy" ]]; then
854
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
855
- print_error "Not a git repository!"
856
- exit 1
857
- fi
853
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
854
+ print_error "Not a git repository!"
855
+ exit 1
856
+ fi
858
857
 
859
- if [[ -z "$CLI_MSG" ]]; then
860
- print_error "Error: Commit message required for Lazy Wizard (-z)"
861
- echo "Usage: bimagic -z \"commit message\""
862
- exit 1
863
- fi
858
+ if [[ -z "$CLI_MSG" ]]; then
859
+ print_error "Error: Commit message required for Lazy Wizard (-z)"
860
+ echo "Usage: bimagic -z \"commit message\""
861
+ exit 1
862
+ fi
864
863
 
865
- print_status " Lazy Wizard invoked!"
864
+ print_status " Lazy Wizard invoked!"
866
865
 
867
- # 1. Add all changes
868
- if gum spin --title "Adding files..." -- git add .; then
869
- print_status "Files added."
870
- else
871
- print_error "Failed to add files."
872
- exit 1
873
- fi
866
+ # 1. Add all changes
867
+ if gum spin --title "Adding files..." -- git add .; then
868
+ print_status "Files added."
869
+ else
870
+ print_error "Failed to add files."
871
+ exit 1
872
+ fi
874
873
 
875
- # 2. Commit
876
- if git commit -m "$CLI_MSG"; then
877
- print_status "Committed: $CLI_MSG"
874
+ # 2. Commit
875
+ if git commit -m "$CLI_MSG"; then
876
+ print_status "Committed: $CLI_MSG"
877
+ else
878
+ print_error "Commit failed (nothing to commit?)"
879
+ exit 1
880
+ fi
881
+
882
+ # 3. Push
883
+ branch=$(get_current_branch)
884
+ print_status "Pushing to $branch..."
885
+
886
+ if gum spin --title "Pushing..." -- git push; then
887
+ print_status "󱝂 Magic complete!"
888
+ else
889
+ # Try setting upstream if standard push failed
890
+ print_warning "Standard push failed. Trying to set upstream..."
891
+ if gum spin --title "Pushing (upstream)..." -- git push -u origin "$branch"; then
892
+ print_status "󱝂 Magic complete (upstream set)!"
878
893
  else
879
- print_error "Commit failed (nothing to commit?)"
880
- exit 1
894
+ print_error "Push failed."
895
+ exit 1
881
896
  fi
882
-
883
- # 3. Push
884
- branch=$(get_current_branch)
885
- print_status "Pushing to $branch..."
886
-
887
- if gum spin --title "Pushing..." -- git push; then
888
- print_status "󱝂 Magic complete!"
889
- else
890
- # Try setting upstream if standard push failed
891
- print_warning "Standard push failed. Trying to set upstream..."
892
- if gum spin --title "Pushing (upstream)..." -- git push -u origin "$branch"; then
893
- print_status "󱝂 Magic complete (upstream set)!"
894
- else
895
- print_error "Push failed."
896
- exit 1
897
- fi
898
- fi
899
- exit 0
897
+ fi
898
+ exit 0
900
899
  fi
901
900
 
902
901
  CONFIG_DIR="$HOME/.config/bimagic"
903
902
  VERSION_FILE="$CONFIG_DIR/version"
904
903
 
905
- show_welcome_banner() {
906
- clear
907
- echo -e "$(get_ansi_esc "$BANNER_COLOR_1")▗▖ ▄ ▄▄▄▄ ▗▄▖ ▗▄▄▖▄ ▗▄▄▖\033[0m"
908
- echo -e "$(get_ansi_esc "$BANNER_COLOR_2")▐▌ ▄ █ █ ▐▌ ▐▌▐▌ ▄ ▐▌ \033[0m"
909
- echo -e "$(get_ansi_esc "$BANNER_COLOR_3")▐▛▀▚▖█ █ █ ▐▛▀▜▌▐▌▝▜▌█ ▐▌ \033[0m"
910
- echo -e "$(get_ansi_esc "$BANNER_COLOR_4")▐▙▄▞▘█ ▐▌ ▐▌▝▚▄▞▘█ ▝▚▄▄▖\033[0m"
911
- echo -e "$(get_ansi_esc "$BANNER_COLOR_5") \033[0m"
912
-
913
- echo
914
- gum style --foreground "$BIMAGIC_PRIMARY" --bold "✨ Welcome to Bimagic Git Wizard $VERSION ✨"
915
- echo
916
-
917
- if [[ ! -f "$VERSION_FILE" ]]; then
918
- gum style --foreground "$BIMAGIC_SUCCESS" "It looks like this is your first time using Bimagic! Let's cast some spells."
919
- else
920
- gum style --foreground "$BIMAGIC_SUCCESS" "Bimagic has been updated to $VERSION! Enjoy the new magic."
904
+ resurrect_commit() {
905
+ print_status "󰔪 Summoning the Resurrection Stone..."
906
+
907
+ # Check if reflog has entries
908
+ if [[ -z "$(git reflog 2>/dev/null)" ]]; then
909
+ print_error "The ancient logs are empty. Nothing to resurrect."
910
+ return 1
911
+ fi
912
+
913
+ echo "Search the ancient timelines for your lost code:"
914
+ # Use gum filter to search through the entire reflog
915
+ local selected_log=$(git reflog --date=relative --format="%h %gd %C(blue)%ad%Creset %s" | gum filter --placeholder "Search for a lost commit message or hash...")
916
+
917
+ if [[ -z "$selected_log" ]]; then
918
+ print_status "Resurrection cancelled."
919
+ return 0
920
+ fi
921
+
922
+ # Extract the hash (the first word)
923
+ local target_hash=$(echo "$selected_log" | awk '{print $1}')
924
+
925
+ echo
926
+ gum style --border rounded --border-foreground "$BIMAGIC_PRIMARY" --padding "1 2" \
927
+ "TARGET TIMELINE:" "$selected_log"
928
+ echo
929
+
930
+ # Give the user options on how to resurrect
931
+ local action=$(gum choose "󰔱 Create a new branch here (Safest)" " Hard Reset current branch to here (Dangerous)" "Cancel")
932
+
933
+ case "$action" in
934
+ "󰔱 Create a new branch here (Safest)")
935
+ local new_branch=$(gum input --placeholder "Enter new branch name (e.g., recovered-code)")
936
+ if [[ -n "$new_branch" ]]; then
937
+ git checkout -b "$new_branch" "$target_hash"
938
+ print_status "󱝁 Timeline restored! You are now on branch: $new_branch"
921
939
  fi
922
- echo
923
-
924
- # Save the current version so this doesn't show again until the next update
925
- mkdir -p "$CONFIG_DIR"
926
- echo "$VERSION" >"$VERSION_FILE"
940
+ ;;
941
+ " Hard Reset current branch to here (Dangerous)")
942
+ if gum confirm "This will overwrite your CURRENT work. Are you absolutely sure?"; then
943
+ git reset --hard "$target_hash"
944
+ print_status " Timeline overwritten. Welcome back."
945
+ fi
946
+ ;;
947
+ *)
948
+ print_status "The stone goes dormant."
949
+ ;;
950
+ esac
951
+ }
927
952
 
928
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to open the spellbook..."
929
- read -r </dev/tty
953
+ show_welcome_banner() {
954
+ clear
955
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_1")▗▖ ▄ ▄▄▄▄ ▗▄▖ ▗▄▄▖▄ ▗▄▄▖\033[0m"
956
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_2")▐▌ ▄ █ █ █ ▐▌ ▐▌▐▌ ▄ ▐▌ \033[0m"
957
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_3")▐▛▀▚▖█ █ █ ▐▛▀▜▌▐▌▝▜▌█ ▐▌ \033[0m"
958
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_4")▐▙▄▞▘█ ▐▌ ▐▌▝▚▄▞▘█ ▝▚▄▄▖\033[0m"
959
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_5") \033[0m"
960
+
961
+ echo
962
+ gum style --foreground "$BIMAGIC_PRIMARY" --bold "✨ Welcome to Bimagic Git Wizard $VERSION ✨"
963
+ echo
964
+
965
+ if [[ ! -f "$VERSION_FILE" ]]; then
966
+ gum style --foreground "$BIMAGIC_SUCCESS" "It looks like this is your first time using Bimagic! Let's cast some spells."
967
+ else
968
+ gum style --foreground "$BIMAGIC_SUCCESS" "Bimagic has been updated to $VERSION! Enjoy the new magic."
969
+ fi
970
+ echo
971
+
972
+ # Save the current version so this doesn't show again until the next update
973
+ mkdir -p "$CONFIG_DIR"
974
+ echo "$VERSION" >"$VERSION_FILE"
975
+
976
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to open the spellbook..."
977
+ read -r </dev/tty
930
978
  }
931
979
 
932
980
  if [[ -z "$CLI_MODE" ]]; then
933
- # Check if the version file doesn't exist, OR if it doesn't match the current version
934
- if [[ ! -f "$VERSION_FILE" ]] || [[ "$(cat "$VERSION_FILE")" != "$VERSION" ]]; then
935
- show_welcome_banner
936
- fi
981
+ # Check if the version file doesn't exist, OR if it doesn't match the current version
982
+ if [[ ! -f "$VERSION_FILE" ]] || [[ "$(cat "$VERSION_FILE")" != "$VERSION" ]]; then
983
+ show_welcome_banner
984
+ fi
937
985
  fi
938
986
 
939
987
  while true; do
940
- clear
941
- show_repo_status # <-- shows the status dashboard at the top
942
- # gum menu
943
- choice=$(
944
- gum choose --header " Choose your spell: (j/k to navigate)" \
945
- --cursor " " --cursor.foreground "$BIMAGIC_PRIMARY" \
946
- " Clone repository" \
947
- " Init new repo" \
948
- " Add files" \
949
- " Commit changes" \
950
- " Push to remote" \
951
- " Pull latest changes" \
952
- " Create/switch branch" \
953
- " Set remote" \
954
- "󱖫 Show status" \
955
- " Contributor Statistics" \
956
- " Git graph" \
957
- "󰓗 Summon the Architect (.gitignore)" \
958
- "󰮘 Remove files/folders (rm)" \
959
- " Merge branches" \
960
- " Uninitialize repo" \
961
- "󰁯 Revert commit(s)" \
962
- "󰓗 Stash operations" \
963
- "󰿅 Exit"
964
- )
965
- echo
966
-
967
- case "$choice" in
968
- " Clone repository")
969
- repo_url=$(gum input --placeholder "Enter repository URL")
970
- if [[ -z "$repo_url" ]]; then continue; fi
971
-
972
- repo_depth=$(gum input --placeholder "Enter depth (empty for full clone)")
973
-
974
- clone_mode=$(gum choose "Standard Clone" "Interactive (Select files)")
975
-
976
- if [[ "$clone_mode" == "Interactive (Select files)" ]]; then
977
- clone_repo "$repo_url" "true" "$repo_depth"
988
+ clear
989
+ show_repo_status # <-- shows the status dashboard at the top
990
+ # gum menu
991
+ choice=$(
992
+ gum choose --header " Choose your spell: (j/k to navigate)" \
993
+ --cursor " " --cursor.foreground "$BIMAGIC_PRIMARY" \
994
+ " Clone repository" \
995
+ " Init new repo" \
996
+ " Add files" \
997
+ " Commit changes" \
998
+ " Push to remote" \
999
+ " Pull latest changes" \
1000
+ " Create/switch branch" \
1001
+ " Set remote" \
1002
+ "󱖫 Show status" \
1003
+ " Contributor Statistics" \
1004
+ " Git graph" \
1005
+ "󰓗 Summon the Architect (.gitignore)" \
1006
+ "󰮘 Remove files/folders (rm)" \
1007
+ " Merge branches" \
1008
+ " Uninitialize repo" \
1009
+ "󰔪 Summon the Resurrection Stone (Recover lost code)" \
1010
+ "󰁯 Revert commit(s)" \
1011
+ "󰓗 Stash operations" \
1012
+ "󰿅 Exit"
1013
+ )
1014
+ echo
1015
+
1016
+ case "$choice" in
1017
+ " Clone repository")
1018
+ repo_url=$(gum input --placeholder "Enter repository URL")
1019
+ if [[ -z "$repo_url" ]]; then continue; fi
1020
+
1021
+ repo_depth=$(gum input --placeholder "Enter depth (empty for full clone)")
1022
+
1023
+ clone_mode=$(gum choose "Standard Clone" "Interactive (Select files)")
1024
+
1025
+ if [[ "$clone_mode" == "Interactive (Select files)" ]]; then
1026
+ clone_repo "$repo_url" "true" "$repo_depth"
1027
+ else
1028
+ clone_repo "$repo_url" "false" "$repo_depth"
1029
+ fi
1030
+ ;;
1031
+ "󰓗 Stash operations")
1032
+ while true; do
1033
+ stash_choice=$(gum choose --header "Stash Operations" \
1034
+ "󱉛 Push (Save) changes" \
1035
+ "󱉙 Pop latest stash" \
1036
+ " List stashes" \
1037
+ " Apply specific stash" \
1038
+ " Drop specific stash" \
1039
+ "󰎟 Clear all stashes" \
1040
+ "󰌍 Back")
1041
+
1042
+ case "$stash_choice" in
1043
+ "󱉛 Push (Save) changes")
1044
+ msg=$(gum input --placeholder "Optional stash message")
1045
+ # Ask about untracked files
1046
+ if gum confirm "Include untracked files?"; then
1047
+ include_untracked="-u"
978
1048
  else
979
- clone_repo "$repo_url" "false" "$repo_depth"
980
- fi
981
- ;;
982
- "󰓗 Stash operations")
983
- while true; do
984
- stash_choice=$(gum choose --header "Stash Operations" \
985
- "󱉛 Push (Save) changes" \
986
- "󱉙 Pop latest stash" \
987
- " List stashes" \
988
- " Apply specific stash" \
989
- " Drop specific stash" \
990
- "󰎟 Clear all stashes" \
991
- "󰌍 Back")
992
-
993
- case "$stash_choice" in
994
- "󱉛 Push (Save) changes")
995
- msg=$(gum input --placeholder "Optional stash message")
996
- # Ask about untracked files
997
- if gum confirm "Include untracked files?"; then
998
- include_untracked="-u"
999
- else
1000
- include_untracked=""
1001
- fi
1002
-
1003
- if git stash push $include_untracked -m "$msg"; then
1004
- print_status "Changes stashed successfully!"
1005
- else
1006
- print_error "Failed to stash changes."
1007
- fi
1008
- ;;
1009
- "󱉙 Pop latest stash")
1010
- if git stash pop; then
1011
- print_status "Stash popped successfully!"
1012
- else
1013
- print_error "Failed to pop stash (possible conflicts)."
1014
- fi
1015
- ;;
1016
- " List stashes")
1017
- if [[ -z "$(git stash list)" ]]; then
1018
- print_warning "No stashes found."
1019
- else
1020
- git stash list | gum style --border normal --padding "0 1"
1021
- fi
1022
- ;;
1023
- " Apply specific stash")
1024
- if [[ -z "$(git stash list)" ]]; then
1025
- print_warning "No stashes found."
1026
- continue
1027
- fi
1028
-
1029
- stash_entry=$(git stash list | gum filter --placeholder "Select stash to apply")
1030
- if [[ -n "$stash_entry" ]]; then
1031
- stash_id=$(echo "$stash_entry" | cut -d: -f1)
1032
- if git stash apply "$stash_id"; then
1033
- print_status "Applied $stash_id"
1034
- else
1035
- print_error "Failed to apply $stash_id"
1036
- fi
1037
- fi
1038
- ;;
1039
- " Drop specific stash")
1040
- if [[ -z "$(git stash list)" ]]; then
1041
- print_warning "No stashes found."
1042
- continue
1043
- fi
1044
-
1045
- stash_entry=$(git stash list | gum filter --placeholder "Select stash to drop")
1046
- if [[ -n "$stash_entry" ]]; then
1047
- stash_id=$(echo "$stash_entry" | cut -d: -f1)
1048
- if gum confirm "Are you sure you want to drop $stash_id?"; then
1049
- if git stash drop "$stash_id"; then
1050
- print_status "Dropped $stash_id"
1051
- else
1052
- print_error "Failed to drop $stash_id"
1053
- fi
1054
- fi
1055
- fi
1056
- ;;
1057
- "󰎟 Clear all stashes")
1058
- if [[ -z "$(git stash list)" ]]; then
1059
- print_warning "No stashes found."
1060
- continue
1061
- fi
1062
-
1063
- if gum confirm "DANGER: This will delete ALL stashes. Continue?"; then
1064
- if git stash clear; then
1065
- print_status "All stashes cleared."
1066
- else
1067
- print_error "Failed to clear stashes."
1068
- fi
1069
- else
1070
- print_status "Operation cancelled."
1071
- fi
1072
- ;;
1073
- "󰌍 Back")
1074
- break
1075
- ;;
1076
- esac
1077
- echo
1078
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1079
- read -r </dev/tty
1080
- done
1081
- ;;
1082
- " Init new repo")
1083
- dirname=$(gum input --placeholder "Enter repo directory name (or '.' for current dir)")
1084
- if [[ -z "$dirname" ]]; then
1085
- print_warning "Operation cancelled."
1086
- continue
1049
+ include_untracked=""
1087
1050
  fi
1088
1051
 
1089
- if [[ "$dirname" == "." ]]; then
1090
- git init
1091
- print_status "Repo initialized in current directory: $(pwd)"
1052
+ if git stash push $include_untracked -m "$msg"; then
1053
+ print_status "Changes stashed successfully!"
1092
1054
  else
1093
- mkdir -p "$dirname"
1094
- # Run init and rename in a subshell to avoid directory tracking issues
1095
- (
1096
- cd "$dirname" || exit 1
1097
- git init
1098
- current_branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1099
- if [[ "$current_branch" == "master" ]]; then
1100
- git branch -M main
1101
- echo "Default branch renamed from 'master' to 'main' in $dirname"
1102
- fi
1103
- )
1104
- print_status "Repo initialized in new directory: $dirname"
1055
+ print_error "Failed to stash changes."
1105
1056
  fi
1106
1057
  ;;
1107
- " Add files")
1108
- # Show untracked + modified files, plus an [ALL] option
1109
- files=$( (
1110
- echo "[ALL]"
1111
- git ls-files --others --modified --exclude-standard
1112
- ) | gum filter --no-limit --placeholder "Select files to add")
1113
-
1114
- if [[ -z "$files" ]]; then
1115
- print_warning "No files selected."
1058
+ "󱉙 Pop latest stash")
1059
+ if git stash pop; then
1060
+ print_status "Stash popped successfully!"
1116
1061
  else
1117
- # gum filter returns selected items separated by newlines.
1118
- # We need to handle the case where [ALL] is selected along with other files.
1119
- if echo "$files" | grep -q "\[ALL\]"; then
1120
- git add .
1121
- print_status "All files staged."
1122
- else
1123
- # Correctly handle filenames with spaces by reading line by line
1124
- echo "$files" | while read -r f; do
1125
- [[ -n "$f" ]] && git add "$f"
1126
- done
1127
- print_status "Selected files staged."
1128
- # Optionally, list the files that were staged
1129
- echo "$files"
1130
- fi
1062
+ print_error "Failed to pop stash (possible conflicts)."
1131
1063
  fi
1132
1064
  ;;
1133
- " Commit changes")
1134
- commit_mode=$(gum choose "󰦥 Magic Commit (Builder)" "󱐋 Quick Commit (One-line)")
1135
-
1136
- if [[ "$commit_mode" == "󰦥 Magic Commit (Builder)" ]]; then
1137
- commit_wizard
1065
+ " List stashes")
1066
+ if [[ -z "$(git stash list)" ]]; then
1067
+ print_warning "No stashes found."
1138
1068
  else
1139
- msg=$(gum input --placeholder "Enter commit message")
1140
- if [[ -z "$msg" ]]; then
1141
- print_warning "No commit message provided. Cancelled."
1142
- continue
1143
- fi
1144
-
1145
- if gum confirm "Commit changes?"; then
1146
- git commit -m "$msg"
1147
- print_status "Commit done!"
1148
- else
1149
- print_status "Commit cancelled."
1150
- fi
1069
+ git stash list | gum style --border normal --padding "0 1"
1151
1070
  fi
1152
1071
  ;;
1153
- " Push to remote")
1154
- branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1155
- branch=${branch:-main}
1156
-
1157
- remotes=$(git remote)
1158
-
1159
- if [[ -z "$remotes" ]]; then
1160
- print_error "No remote set!"
1161
- if setup_remote "origin"; then
1162
- remote="origin"
1163
- else
1164
- continue
1165
- fi
1166
- else
1167
- # Select remote if multiple exist
1168
- if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1169
- remote="$remotes"
1170
- else
1171
- remote=$(echo "$remotes" | gum filter --placeholder "Select remote to push to")
1172
- fi
1072
+ " Apply specific stash")
1073
+ if [[ -z "$(git stash list)" ]]; then
1074
+ print_warning "No stashes found."
1075
+ continue
1173
1076
  fi
1174
1077
 
1175
- [[ -z "$remote" ]] && continue
1176
-
1177
- if gum confirm "Push branch '$branch' to '$remote'?"; then
1178
- echo "Pushing branch '$branch' to '$remote'..."
1179
- gum spin --title "Pushing..." -- git push -u "$remote" "$branch"
1180
- else
1181
- print_status "Push cancelled."
1078
+ stash_entry=$(git stash list | gum filter --placeholder "Select stash to apply")
1079
+ if [[ -n "$stash_entry" ]]; then
1080
+ stash_id=$(echo "$stash_entry" | cut -d: -f1)
1081
+ if git stash apply "$stash_id"; then
1082
+ print_status "Applied $stash_id"
1083
+ else
1084
+ print_error "Failed to apply $stash_id"
1085
+ fi
1182
1086
  fi
1183
1087
  ;;
1184
- " Pull latest changes")
1185
- # Auto-fetch before pulling
1186
- if gum spin --title "Fetching updates..." -- git fetch --all; then
1187
- print_status "Fetch complete."
1188
- else
1189
- print_warning "Fetch encountered issues."
1088
+ " Drop specific stash")
1089
+ if [[ -z "$(git stash list)" ]]; then
1090
+ print_warning "No stashes found."
1091
+ continue
1190
1092
  fi
1191
1093
 
1192
- pull_choice=$(gum choose --header "Select pull mode" \
1193
- "Pull specific branch" \
1194
- "Pull all")
1195
-
1196
- case "$pull_choice" in
1197
- "Pull all")
1198
- if gum confirm "Run 'git pull --all'?"; then
1199
- gum spin --title "Pulling all..." -- git pull --all
1200
- print_status "Pull all complete."
1201
- else
1202
- print_status "Pull cancelled."
1203
- fi
1204
- ;;
1205
- "Pull specific branch")
1206
- branch=$(gum input --value "main" --placeholder "Enter branch to pull")
1207
- branch=${branch:-main}
1208
-
1209
- remotes=$(git remote 2>/dev/null)
1210
- if [[ -z "$remotes" ]]; then
1211
- print_error "No remote set! Cannot pull."
1212
- else
1213
- # Select remote if multiple exist
1214
- if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1215
- remote="$remotes"
1216
- else
1217
- remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
1218
- fi
1219
-
1220
- if [[ -n "$remote" ]]; then
1221
- if gum confirm "Pull branch '$branch' from '$remote'?"; then
1222
- gum spin --title "Pulling..." -- git pull "$remote" "$branch"
1223
- else
1224
- print_status "Pull cancelled."
1225
- fi
1226
- else
1227
- print_warning "No remote selected."
1228
- fi
1229
- fi
1230
- ;;
1231
- esac
1232
- ;;
1233
- " Create/switch branch")
1234
- # Show current branch and all available branches
1235
- current_branch=$(get_current_branch)
1236
- print_status "Current branch: $current_branch"
1237
- echo
1238
- print_status "Available branches:"
1239
- show_branches
1240
- echo
1241
-
1242
- # Let user choose between existing branches or new branch
1243
- branch_option=$(gum choose "Switch to existing branch" "Create new branch")
1244
-
1245
- case "$branch_option" in
1246
- "Switch to existing branch")
1247
- # Use gum filter to select from existing branches
1248
- existing_branch=$(git branch --format='%(refname:short)' | gum filter --placeholder "Select branch to switch to")
1249
- if [[ -n "$existing_branch" ]]; then
1250
- git checkout "$existing_branch"
1251
- print_status "Switched to branch: $existing_branch"
1094
+ stash_entry=$(git stash list | gum filter --placeholder "Select stash to drop")
1095
+ if [[ -n "$stash_entry" ]]; then
1096
+ stash_id=$(echo "$stash_entry" | cut -d: -f1)
1097
+ if gum confirm "Are you sure you want to drop $stash_id?"; then
1098
+ if git stash drop "$stash_id"; then
1099
+ print_status "Dropped $stash_id"
1252
1100
  else
1253
- print_warning "No branch selected."
1101
+ print_error "Failed to drop $stash_id"
1254
1102
  fi
1255
- ;;
1256
- "Create new branch")
1257
- new_branch=$(gum input --placeholder "Enter new branch name")
1258
- if [[ -n "$new_branch" ]]; then
1259
- git checkout -b "$new_branch"
1260
- print_status "Created and switched to new branch: $new_branch"
1261
- else
1262
- print_error "No branch name provided."
1263
- fi
1264
- ;;
1265
- *)
1266
- print_warning "Operation cancelled."
1267
- ;;
1268
- esac
1269
- ;;
1270
- " Set remote")
1271
- setup_remote "origin"
1272
- ;;
1273
- "󱖫 Show status")
1274
- git status
1103
+ fi
1104
+ fi
1275
1105
  ;;
1276
- "󰮘 Remove files/folders (rm)")
1277
- # List tracked + untracked files for removal
1278
- files=$(git ls-files --cached --others --exclude-standard |
1279
- gum filter --no-limit --placeholder "Select files/folders to remove")
1280
-
1281
- if [[ -z "$files" ]]; then
1282
- print_warning "No files selected."
1283
- continue # Use continue to go back to the main menu
1106
+ "󰎟 Clear all stashes")
1107
+ if [[ -z "$(git stash list)" ]]; then
1108
+ print_warning "No stashes found."
1109
+ continue
1284
1110
  fi
1285
1111
 
1286
- echo "Files selected for removal:"
1287
- # Use 'tput' for better visual separation and color
1288
- tput setaf 3
1289
- echo "$files"
1290
- tput sgr0
1291
- echo
1292
-
1293
- if gum confirm "Confirm removal? This cannot be undone."; then
1294
- # Use a 'while read' loop to correctly handle filenames with spaces
1295
- echo "$files" | while read -r f; do
1296
- if [[ -z "$f" ]]; then continue; fi # Skip empty lines
1297
-
1298
- # Check if the file is tracked by git
1299
- if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
1300
- # It's a tracked file, use 'git rm'
1301
- git rm -rf "$f"
1302
- else
1303
- # It's an untracked file, use 'rm'
1304
- rm -rf "$f"
1305
- fi
1306
- done
1307
- print_status "Selected files/folders have been removed."
1112
+ if gum confirm "DANGER: This will delete ALL stashes. Continue?"; then
1113
+ if git stash clear; then
1114
+ print_status "All stashes cleared."
1115
+ else
1116
+ print_error "Failed to clear stashes."
1117
+ fi
1308
1118
  else
1309
- print_status "Operation cancelled."
1119
+ print_status "Operation cancelled."
1310
1120
  fi
1311
1121
  ;;
1312
- " Uninitialize repo")
1313
- print_warning "This will completely uninitialize the Git repository in this folder."
1314
- echo "This action will delete the .git directory and cannot be undone!"
1315
- echo
1316
-
1317
- if gum confirm "Are you sure you want to continue?"; then
1318
- if [ -d ".git" ]; then
1319
- rm -rf .git
1320
- print_status "Git repository has been uninitialized."
1321
- else
1322
- print_error "No .git directory found here. Nothing to do."
1323
- fi
1324
- else
1325
- print_status "Operation cancelled."
1326
- fi
1122
+ "󰌍 Back")
1123
+ break
1327
1124
  ;;
1328
- "󰿅 Exit")
1329
- if gum confirm "Are you sure you want to exit?"; then
1330
- echo "Git Wizard vanishes in a puff of smoke..."
1331
- exit 0
1125
+ esac
1126
+ echo
1127
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1128
+ read -r </dev/tty
1129
+ done
1130
+ ;;
1131
+ " Init new repo")
1132
+ dirname=$(gum input --placeholder "Enter repo directory name (or '.' for current dir)")
1133
+ if [[ -z "$dirname" ]]; then
1134
+ print_warning "Operation cancelled."
1135
+ continue
1136
+ fi
1137
+
1138
+ if [[ "$dirname" == "." ]]; then
1139
+ git init
1140
+ print_status "Repo initialized in current directory: $(pwd)"
1141
+ else
1142
+ mkdir -p "$dirname"
1143
+ # Run init and rename in a subshell to avoid directory tracking issues
1144
+ (
1145
+ cd "$dirname" || exit 1
1146
+ git init
1147
+ current_branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1148
+ if [[ "$current_branch" == "master" ]]; then
1149
+ git branch -M main
1150
+ echo "Default branch renamed from 'master' to 'main' in $dirname"
1151
+ fi
1152
+ )
1153
+ print_status "Repo initialized in new directory: $dirname"
1154
+ fi
1155
+ ;;
1156
+ " Add files")
1157
+ # Show untracked + modified files, plus an [ALL] option
1158
+ files=$( (
1159
+ echo "[ALL]"
1160
+ git ls-files --others --modified --exclude-standard
1161
+ ) | gum filter --no-limit --placeholder "Select files to add")
1162
+
1163
+ if [[ -z "$files" ]]; then
1164
+ print_warning "No files selected."
1165
+ else
1166
+ # gum filter returns selected items separated by newlines.
1167
+ # We need to handle the case where [ALL] is selected along with other files.
1168
+ if echo "$files" | grep -q "\[ALL\]"; then
1169
+ git add .
1170
+ print_status "All files staged."
1171
+ else
1172
+ # Correctly handle filenames with spaces by reading line by line
1173
+ echo "$files" | while read -r f; do
1174
+ [[ -n "$f" ]] && git add "$f"
1175
+ done
1176
+ print_status "Selected files staged."
1177
+ # Optionally, list the files that were staged
1178
+ echo "$files"
1179
+ fi
1180
+ fi
1181
+ ;;
1182
+ " Commit changes")
1183
+ commit_mode=$(gum choose "󰦥 Magic Commit (Builder)" "󱐋 Quick Commit (One-line)")
1184
+
1185
+ if [[ "$commit_mode" == "󰦥 Magic Commit (Builder)" ]]; then
1186
+ commit_wizard
1187
+ else
1188
+ msg=$(gum input --placeholder "Enter commit message")
1189
+ if [[ -z "$msg" ]]; then
1190
+ print_warning "No commit message provided. Cancelled."
1191
+ continue
1192
+ fi
1193
+
1194
+ if gum confirm "Commit changes?"; then
1195
+ git commit -m "$msg"
1196
+ print_status "Commit done!"
1197
+ else
1198
+ print_status "Commit cancelled."
1199
+ fi
1200
+ fi
1201
+ ;;
1202
+ " Push to remote")
1203
+ branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1204
+ branch=${branch:-main}
1205
+
1206
+ remotes=$(git remote)
1207
+
1208
+ if [[ -z "$remotes" ]]; then
1209
+ print_error "No remote set!"
1210
+ if setup_remote "origin"; then
1211
+ remote="origin"
1212
+ else
1213
+ continue
1214
+ fi
1215
+ else
1216
+ # Select remote if multiple exist
1217
+ if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1218
+ remote="$remotes"
1219
+ else
1220
+ remote=$(echo "$remotes" | gum filter --placeholder "Select remote to push to")
1221
+ fi
1222
+ fi
1223
+
1224
+ [[ -z "$remote" ]] && continue
1225
+
1226
+ if gum confirm "Push branch '$branch' to '$remote'?"; then
1227
+ echo "Pushing branch '$branch' to '$remote'..."
1228
+ gum spin --title "Pushing..." -- git push -u "$remote" "$branch"
1229
+ else
1230
+ print_status "Push cancelled."
1231
+ fi
1232
+ ;;
1233
+ " Pull latest changes")
1234
+ # Auto-fetch before pulling
1235
+ if gum spin --title "Fetching updates..." -- git fetch --all; then
1236
+ print_status "Fetch complete."
1237
+ else
1238
+ print_warning "Fetch encountered issues."
1239
+ fi
1240
+
1241
+ pull_choice=$(gum choose --header "Select pull mode" \
1242
+ "Pull specific branch" \
1243
+ "Pull all")
1244
+
1245
+ case "$pull_choice" in
1246
+ "Pull all")
1247
+ if gum confirm "Run 'git pull --all'?"; then
1248
+ gum spin --title "Pulling all..." -- git pull --all
1249
+ print_status "Pull all complete."
1250
+ else
1251
+ print_status "Pull cancelled."
1252
+ fi
1253
+ ;;
1254
+ "Pull specific branch")
1255
+ branch=$(gum input --value "main" --placeholder "Enter branch to pull")
1256
+ branch=${branch:-main}
1257
+
1258
+ remotes=$(git remote 2>/dev/null)
1259
+ if [[ -z "$remotes" ]]; then
1260
+ print_error "No remote set! Cannot pull."
1261
+ else
1262
+ # Select remote if multiple exist
1263
+ if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1264
+ remote="$remotes"
1332
1265
  else
1333
- continue
1266
+ remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
1334
1267
  fi
1335
- ;;
1336
- " Merge branches")
1337
- current_branch=$(get_current_branch)
1338
- print_status "You are on branch: $current_branch"
1339
- echo
1340
-
1341
- # Pick branch to merge from
1342
- merge_branch=$(git branch --format='%(refname:short)' |
1343
- grep -v "^$current_branch$" |
1344
- gum filter --placeholder "Select branch to merge into $current_branch")
1345
-
1346
- if [[ -z "$merge_branch" ]]; then
1347
- print_warning "No branch selected. Merge cancelled."
1268
+
1269
+ if [[ -n "$remote" ]]; then
1270
+ if gum confirm "Pull branch '$branch' from '$remote'?"; then
1271
+ gum spin --title "Pulling..." -- git pull "$remote" "$branch"
1272
+ else
1273
+ print_status "Pull cancelled."
1274
+ fi
1348
1275
  else
1349
- if gum confirm "Merge branch '$merge_branch' into '$current_branch'?"; then
1350
- echo "Merging branch '$merge_branch' into '$current_branch'..."
1351
- if gum spin --title "Merging..." -- git merge "$merge_branch"; then
1352
- print_status "Merge successful!"
1353
- else
1354
- print_error "Merge had conflicts! Resolve them manually."
1355
- fi
1356
- else
1357
- print_status "Merge cancelled."
1358
- fi
1276
+ print_warning "No remote selected."
1359
1277
  fi
1360
- ;;
1361
- " Contributor Statistics")
1362
- show_contributor_stats
1363
- ;;
1278
+ fi
1279
+ ;;
1280
+ esac
1281
+ ;;
1282
+ " Create/switch branch")
1283
+ # Show current branch and all available branches
1284
+ current_branch=$(get_current_branch)
1285
+ print_status "Current branch: $current_branch"
1286
+ echo
1287
+ print_status "Available branches:"
1288
+ show_branches
1289
+ echo
1364
1290
 
1365
- "󰓗 Summon the Architect (.gitignore)")
1366
- summon_gitignore
1367
- ;;
1291
+ # Let user choose between existing branches or new branch
1292
+ branch_option=$(gum choose "Switch to existing branch" "Create new branch")
1293
+
1294
+ case "$branch_option" in
1295
+ "Switch to existing branch")
1296
+ # Use gum filter to select from existing branches
1297
+ existing_branch=$(git branch --format='%(refname:short)' | gum filter --placeholder "Select branch to switch to")
1298
+ if [[ -n "$existing_branch" ]]; then
1299
+ git checkout "$existing_branch"
1300
+ print_status "Switched to branch: $existing_branch"
1301
+ else
1302
+ print_warning "No branch selected."
1303
+ fi
1304
+ ;;
1305
+ "Create new branch")
1306
+ new_branch=$(gum input --placeholder "Enter new branch name")
1307
+ if [[ -n "$new_branch" ]]; then
1308
+ git checkout -b "$new_branch"
1309
+ print_status "Created and switched to new branch: $new_branch"
1310
+ else
1311
+ print_error "No branch name provided."
1312
+ fi
1313
+ ;;
1314
+ *)
1315
+ print_warning "Operation cancelled."
1316
+ ;;
1317
+ esac
1318
+ ;;
1319
+ " Set remote")
1320
+ setup_remote "origin"
1321
+ ;;
1322
+ "󱖫 Show status")
1323
+ git status
1324
+ ;;
1325
+ "󰮘 Remove files/folders (rm)")
1326
+ # List tracked + untracked files for removal
1327
+ files=$(git ls-files --cached --others --exclude-standard |
1328
+ gum filter --no-limit --placeholder "Select files/folders to remove")
1329
+
1330
+ if [[ -z "$files" ]]; then
1331
+ print_warning "No files selected."
1332
+ continue # Use continue to go back to the main menu
1333
+ fi
1368
1334
 
1369
- "󰁯 Revert commit(s)")
1370
- print_status "Fetching commit history..."
1371
- echo
1335
+ echo "Files selected for removal:"
1336
+ # Use 'tput' for better visual separation and color
1337
+ tput setaf 3
1338
+ echo "$files"
1339
+ tput sgr0
1340
+ echo
1372
1341
 
1373
- # Show commits as "<hash> <message>" but keep hash separately
1374
- commits=$(git log --oneline --decorate |
1375
- gum filter --no-limit --placeholder "Select commit(s) to revert" |
1376
- awk '{print $1}')
1342
+ if gum confirm "Confirm removal? This cannot be undone."; then
1343
+ # Use a 'while read' loop to correctly handle filenames with spaces
1344
+ echo "$files" | while read -r f; do
1345
+ if [[ -z "$f" ]]; then continue; fi # Skip empty lines
1377
1346
 
1378
- if [[ -z "$commits" ]]; then
1379
- print_warning "No commit selected. Revert cancelled."
1380
- continue
1347
+ # Check if the file is tracked by git
1348
+ if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
1349
+ # It's a tracked file, use 'git rm'
1350
+ git rm -rf "$f"
1351
+ else
1352
+ # It's an untracked file, use 'rm'
1353
+ rm -rf "$f"
1381
1354
  fi
1355
+ done
1356
+ print_status "Selected files/folders have been removed."
1357
+ else
1358
+ print_status "Operation cancelled."
1359
+ fi
1360
+ ;;
1361
+ " Uninitialize repo")
1362
+ print_warning "This will completely uninitialize the Git repository in this folder."
1363
+ echo "This action will delete the .git directory and cannot be undone!"
1364
+ echo
1382
1365
 
1383
- echo "You selected:"
1384
- echo "$commits"
1385
- echo
1386
-
1387
- if gum confirm "Confirm revert?"; then
1388
- for c in $commits; do
1389
- echo "Reverting commit $c..."
1390
- if git revert --no-edit "$c"; then
1391
- print_status "Commit $c reverted."
1392
- else
1393
- print_error "Conflict occurred while reverting $c!"
1394
- echo "Please resolve conflicts, then run:"
1395
- echo " git revert --continue"
1396
- break
1397
- fi
1398
- done
1366
+ if gum confirm "Are you sure you want to continue?"; then
1367
+ if [ -d ".git" ]; then
1368
+ rm -rf .git
1369
+ print_status "Git repository has been uninitialized."
1370
+ else
1371
+ print_error "No .git directory found here. Nothing to do."
1372
+ fi
1373
+ else
1374
+ print_status "Operation cancelled."
1375
+ fi
1376
+ ;;
1377
+ "󰿅 Exit")
1378
+ if gum confirm "Are you sure you want to exit?"; then
1379
+ echo "Git Wizard vanishes in a puff of smoke..."
1380
+ exit 0
1381
+ else
1382
+ continue
1383
+ fi
1384
+ ;;
1385
+ " Merge branches")
1386
+ current_branch=$(get_current_branch)
1387
+ print_status "You are on branch: $current_branch"
1388
+ echo
1389
+
1390
+ # Pick branch to merge from
1391
+ merge_branch=$(git branch --format='%(refname:short)' |
1392
+ grep -v "^$current_branch$" |
1393
+ gum filter --placeholder "Select branch to merge into $current_branch")
1394
+
1395
+ if [[ -z "$merge_branch" ]]; then
1396
+ print_warning "No branch selected. Merge cancelled."
1397
+ else
1398
+ if gum confirm "Merge branch '$merge_branch' into '$current_branch'?"; then
1399
+ echo "Merging branch '$merge_branch' into '$current_branch'..."
1400
+ if gum spin --title "Merging..." -- git merge "$merge_branch"; then
1401
+ print_status "Merge successful!"
1399
1402
  else
1400
- print_status "Revert cancelled."
1403
+ print_error "Merge had conflicts! Resolve them manually."
1401
1404
  fi
1402
- ;;
1405
+ else
1406
+ print_status "Merge cancelled."
1407
+ fi
1408
+ fi
1409
+ ;;
1410
+ " Contributor Statistics")
1411
+ show_contributor_stats
1412
+ ;;
1403
1413
 
1404
- " Git graph")
1405
- color=${YELLOW}
1406
- line1="Git graph"
1407
- line2="[INFO] press 'q' to exit"
1408
-
1409
- echo -e "${color}╭$(printf '─%.0s' $(seq 1 30))╮${NC}"
1410
- printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line1"
1411
- printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line2"
1412
- echo -e "${color}╰$(printf '─%.0s' $(seq 1 30))╯${NC}"
1413
-
1414
- # Use gum spin while loading the graph.
1415
- # Since git log can be long, we pipe it to less if it's too big,
1416
- # but gum spin doesn't work well with interactive pagers.
1417
- # However, for a simple "pretty log", we can just run it.
1418
- gum spin --title "Drawing git graph..." -- sleep 2
1419
- pretty_git_log
1420
- ;;
1414
+ "󰓗 Summon the Architect (.gitignore)")
1415
+ summon_gitignore
1416
+ ;;
1421
1417
 
1422
- *)
1423
- print_error "Invalid choice! Try again."
1424
- echo "Git Wizard vanishes in a puff of smoke..."
1425
- break
1426
- ;;
1427
- esac
1418
+ "󰔪 Summon the Resurrection Stone (Recover lost code)")
1419
+ resurrect_commit
1420
+ ;;
1421
+
1422
+ "󰁯 Revert commit(s)")
1423
+ print_status "Fetching commit history..."
1424
+ echo
1428
1425
 
1426
+ # Show commits as "<hash> <message>" but keep hash separately
1427
+ commits=$(git log --oneline --decorate |
1428
+ gum filter --no-limit --placeholder "Select commit(s) to revert" |
1429
+ awk '{print $1}')
1430
+
1431
+ if [[ -z "$commits" ]]; then
1432
+ print_warning "No commit selected. Revert cancelled."
1433
+ continue
1434
+ fi
1435
+
1436
+ echo "You selected:"
1437
+ echo "$commits"
1429
1438
  echo
1430
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1431
- read -r </dev/tty
1439
+
1440
+ if gum confirm "Confirm revert?"; then
1441
+ for c in $commits; do
1442
+ echo "Reverting commit $c..."
1443
+ if git revert --no-edit "$c"; then
1444
+ print_status "Commit $c reverted."
1445
+ else
1446
+ print_error "Conflict occurred while reverting $c!"
1447
+ echo "Please resolve conflicts, then run:"
1448
+ echo " git revert --continue"
1449
+ break
1450
+ fi
1451
+ done
1452
+ else
1453
+ print_status "Revert cancelled."
1454
+ fi
1455
+ ;;
1456
+
1457
+ " Git graph")
1458
+ color=${YELLOW}
1459
+ line1="Git graph"
1460
+ line2="[INFO] press 'q' to exit"
1461
+
1462
+ echo -e "${color}╭$(printf '─%.0s' $(seq 1 30))╮${NC}"
1463
+ printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line1"
1464
+ printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line2"
1465
+ echo -e "${color}╰$(printf '─%.0s' $(seq 1 30))╯${NC}"
1466
+
1467
+ # Use gum spin while loading the graph.
1468
+ # Since git log can be long, we pipe it to less if it's too big,
1469
+ # but gum spin doesn't work well with interactive pagers.
1470
+ # However, for a simple "pretty log", we can just run it.
1471
+ gum spin --title "Drawing git graph..." -- sleep 2
1472
+ pretty_git_log
1473
+ ;;
1474
+
1475
+ *)
1476
+ print_error "Invalid choice! Try again."
1477
+ echo "Git Wizard vanishes in a puff of smoke..."
1478
+ break
1479
+ ;;
1480
+ esac
1481
+
1482
+ echo
1483
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1484
+ read -r </dev/tty
1432
1485
  done