bimagic 1.4.6 → 1.5.1

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 (4) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +24 -6
  3. package/bimagic +1199 -1152
  4. 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.1"
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,308 @@ 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)
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 ""
435
463
 
436
- if [[ -d "$repo_name" ]]; then
437
- print_error "Directory '$repo_name' already exists."
438
- return 1
464
+ if [[ $status -ne 0 || ! -d "$repo_name" ]]; then
465
+ print_error "Clone failed."
466
+ return 1
439
467
  fi
440
468
 
441
- local depth_arg=""
442
- if [[ -n "$depth" ]]; then
443
- depth_arg="--depth $depth"
444
- fi
469
+ pushd "$repo_name" >/dev/null || return 1
445
470
 
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
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)")
476
+
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
468
483
 
469
- pushd "$repo_name" >/dev/null || 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
470
487
 
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)")
488
+ # Set the selected paths
489
+ echo "$selected_paths" | git sparse-checkout set --stdin
476
490
 
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
491
+ print_status "Downloading selected files..."
492
+ (
493
+ set -o pipefail
494
+ git checkout --progress HEAD 2>&1 | while read -r -d $'\r' line; do
495
+ if [[ $line =~ Updating\ files:\ +([0-9]+)% ]]; then
496
+ draw_progress_bar "Updating Files" "${BASH_REMATCH[1]}"
482
497
  fi
498
+ done
499
+ )
500
+ echo ""
501
+
502
+ popd >/dev/null
503
+ print_status "Successfully cloned selected files into '$repo_name'!"
504
+
505
+ else
506
+ # Standard clone with progress bar
507
+ print_status "Cloning $url into $repo_name..."
508
+
509
+ # Use a subshell with pipefail to catch git clone errors
510
+ (
511
+ set -o pipefail
512
+ git clone --progress $depth_arg "$url" "$repo_name" 2>&1 | while read -r -d $'\r' line; do
513
+ if [[ $line =~ Receiving\ objects:\ +([0-9]+)% ]]; then
514
+ draw_progress_bar "Receiving Objects" "${BASH_REMATCH[1]}"
515
+ elif [[ $line =~ Resolving\ deltas:\ +([0-9]+)% ]]; then
516
+ draw_progress_bar "Resolving Deltas" "${BASH_REMATCH[1]}"
517
+ fi
518
+ done
519
+ )
520
+ local status=$?
521
+ echo "" # New line after progress bar
483
522
 
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
-
523
+ if [[ $status -eq 0 && -d "$repo_name" ]]; then
524
+ print_status "Successfully cloned '$url' into '$repo_name'!"
511
525
  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
526
+ print_error "Clone failed."
527
+ return 1
535
528
  fi
529
+ fi
536
530
  }
537
531
 
538
532
  # a .gitignore generator to your tool
539
533
  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
534
+ # Check for curl
535
+ if ! command -v curl &>/dev/null; then
536
+ print_error "Error: curl is not installed. Required to summon the Architect."
537
+ return 1
538
+ fi
539
+
540
+ print_status "📜 Summoning the Architect..."
541
+
542
+ # Check if .gitignore already exists
543
+ if [[ -f ".gitignore" ]]; then
544
+ print_warning "A .gitignore file already exists in this directory."
545
+ if ! gum confirm "Do you want to overwrite it?"; then
546
+ print_status "Operation cancelled."
547
+ return 0
555
548
  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
549
+ fi
550
+
551
+ # List of popular templates (curated from github/gitignore)
552
+ local templates=(
553
+ "Actionscript" "Ada" "Android" "Angular" "AppEngine" "ArchLinuxPackages" "Autotools"
554
+ "C++" "C" "CMake" "CUDA" "CakePHP" "ChefCookbook" "Clojure" "CodeIgniter" "Composer"
555
+ "Dart" "Delphi" "Dotnet" "Drupal" "Elixir" "Elm" "Erlang" "Flutter" "Fortran"
556
+ "Go" "Godot" "Gradle" "Grails" "Haskell" "Haxe" "Java" "Jekyll" "Joomla" "Julia"
557
+ "Kotlin" "Laravel" "Lua" "Magento" "Maven" "Nextjs" "Nim" "Nix" "Node" "Objective-C"
558
+ "Opa" "Perl" "Phalcon" "PlayFramework" "Prestashop" "Processing" "Python" "Qt"
559
+ "R" "ROS" "Rails" "Ruby" "Rust" "Scala" "Scheme" "Smalltalk" "Swift" "Symfony"
560
+ "Terraform" "TeX" "Unity" "UnrealEngine" "VisualStudio" "WordPress" "Zig"
561
+ )
562
+
563
+ # Use gum filter for a better selection experience
564
+ local template=$(printf "%s\n" "${templates[@]}" | gum filter --placeholder "Search for a blueprint (e.g., Python, Node, Rust)...")
565
+
566
+ if [[ -z "$template" ]]; then
567
+ print_status "Cancelled."
568
+ return 0
569
+ fi
570
+
571
+ # Fetch the template directly from GitHub's official repository
572
+ print_status "Drawing the magic circle for $template..."
573
+
574
+ local url="https://raw.githubusercontent.com/github/gitignore/main/${template}.gitignore"
575
+
576
+ if gum spin --title "Fetching template..." -- curl -sL "$url" -o .gitignore; then
577
+ # Verify the file is not empty (curl might return 404 text if URL is wrong)
578
+ if grep -q "404: Not Found" .gitignore; then
579
+ print_error "Failed to summon template: 404 Not Found at $url"
580
+ rm .gitignore
581
+ return 1
593
582
  fi
583
+ print_status "✨ .gitignore for $template created successfully!"
584
+ else
585
+ print_error "Failed to summon template. Check your internet connection."
586
+ return 1
587
+ fi
594
588
  }
595
589
 
596
590
  # Function for Conventional Commits
597
591
  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}
592
+ echo -e "${PURPLE}=== The Alchemist's Commit ===${NC}"
593
+
594
+ # 1. Select Type
595
+ local type=$(gum choose --header "Select change type:" \
596
+ "feat: A new feature" \
597
+ "fix: A bug fix" \
598
+ "docs: Documentation only changes" \
599
+ "style: Changes that do not affect the meaning of the code" \
600
+ "refactor: A code change that neither fixes a bug nor adds a feature" \
601
+ "perf: A code change that improves performance" \
602
+ "test: Adding missing tests or correcting existing tests" \
603
+ "chore: Changes to the build process or auxiliary tools")
604
+
605
+ # Extract just the type (e.g., "feat") from the selection
606
+ type=$(echo "$type" | cut -d: -f1)
607
+ [[ -z "$type" ]] && return 1
608
+
609
+ # 2. Scope (Optional)
610
+ local scope=$(gum input --placeholder "Scope (optional, e.g., 'login', 'ui'). Press Enter to skip.")
611
+
612
+ # 3. Short Description
613
+ local summary=$(gum input --placeholder "Short description (imperative mood, e.g., 'add generic login')")
614
+ [[ -z "$summary" ]] && print_warning "Summary is required!" && return 1
615
+
616
+ # 4. Long Description (Optional)
617
+ local body=""
618
+ if gum confirm "Add a longer description (body)?"; then
619
+ body=$(gum write --placeholder "Enter detailed description...")
620
+ fi
621
+
622
+ # 5. Breaking Change?
623
+ local breaking=""
624
+ if gum confirm "Is this a BREAKING CHANGE?"; then
625
+ breaking="!"
626
+ fi
627
+
628
+ # Construct the message
629
+ local commit_msg=""
630
+ if [[ -n "$scope" ]]; then
631
+ commit_msg="${type}(${scope})${breaking}: ${summary}"
632
+ else
633
+ commit_msg="${type}${breaking}: ${summary}"
634
+ fi
635
+
636
+ # Append body if exists
637
+ if [[ -n "$body" ]]; then
638
+ commit_msg="${commit_msg}
645
639
 
646
640
  ${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
641
+ fi
642
+
643
+ # Preview and Confirm
644
+ echo
645
+ gum style --border rounded --border-foreground "$BIMAGIC_PRIMARY" --padding "1 2" \
646
+ "PREVIEW:" "$commit_msg"
647
+ echo
648
+
649
+ if gum confirm "Commit with this message?"; then
650
+ git commit -m "$commit_msg"
651
+ print_status "󱝁 Mischief managed! (Commit successful)"
652
+ else
653
+ print_warning "Commit cancelled."
654
+ fi
661
655
  }
662
656
 
663
657
  #function to display git graph
664
658
  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
659
+ git log --graph \
660
+ --abbrev-commit \
661
+ --decorate \
662
+ --date=short \
663
+ --format="%C(auto)%h%Creset %C(blue)%ad%Creset %C(green)%an%Creset %C(yellow)%d%Creset %Creset%s" \
664
+ --all
671
665
  }
672
666
 
673
667
  # Check if environment variables are set (optional for SSH)
674
668
  if [[ -z "$GITHUB_USER" ]] || [[ -z "$GITHUB_TOKEN" ]]; then
675
- print_warning "GITHUB_USER or GITHUB_TOKEN not set. Defaulting to SSH/Local mode."
669
+ print_warning "GITHUB_USER or GITHUB_TOKEN not set. Defaulting to SSH/Local mode."
676
670
  fi
677
671
 
678
672
  # Parse CLI arguments
@@ -684,749 +678,802 @@ CLI_DEPTH=""
684
678
 
685
679
  # Preserve args in a loop
686
680
  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
681
+ case "$1" in
682
+ -d)
683
+ CLI_MODE="clone"
684
+ shift
685
+ ;;
686
+ --depth)
687
+ CLI_DEPTH="$2"
688
+ shift 2
689
+ ;;
690
+ -i)
691
+ CLI_INTERACTIVE="true"
692
+ shift
693
+ ;;
694
+ -z)
695
+ CLI_MODE="lazy"
696
+ shift
697
+ ;;
698
+ -s)
699
+ CLI_MODE="status"
700
+ shift
701
+ ;;
702
+ -u)
703
+ CLI_MODE="undo"
704
+ shift
705
+ ;;
706
+ -g)
707
+ CLI_MODE="graph"
708
+ shift
709
+ ;;
710
+ -p)
711
+ CLI_MODE="pull"
712
+ shift
713
+ ;;
714
+ -a | --architect)
715
+ CLI_MODE="architect"
716
+ shift
717
+ ;;
718
+ *)
719
+ # Argument handling based on current mode
720
+ if [[ "$CLI_MODE" == "clone" && -z "$CLI_URL" ]]; then
721
+ CLI_URL="$1"
722
+ elif [[ "$CLI_MODE" == "lazy" && -z "$CLI_MSG" ]]; then
723
+ CLI_MSG="$1"
724
+ fi
725
+ shift
726
+ ;;
727
+ esac
734
728
  done
735
729
 
736
730
  # --- Mode Handlers ---
737
731
 
738
732
  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
733
+ if [[ -z "$CLI_URL" ]]; then
734
+ print_error "Error: Repository URL required with -d"
735
+ exit 1
736
+ fi
737
+ clone_repo "$CLI_URL" "$CLI_INTERACTIVE" "$CLI_DEPTH"
738
+ exit 0
745
739
  fi
746
740
 
747
-
748
741
  if [[ "$CLI_MODE" == "status" ]]; then
749
- show_repo_status
750
- exit 0
742
+ show_repo_status
743
+ exit 0
751
744
  fi
752
745
 
753
746
  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
747
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
748
+ print_error "Not a git repository!"
749
+ exit 1
750
+ fi
751
+
752
+ # 1. Fetch
753
+ if gum spin --title "Fetching updates..." -- git fetch --all; then
754
+ print_status "Fetch complete."
755
+ else
756
+ print_warning "Fetch encountered issues during fetch."
757
+ fi
758
+
759
+ # 2. Pull
760
+ if gum spin --title "Pulling all..." -- git pull --all; then
761
+ print_status "Pull all complete."
762
+ else
763
+ print_error "Pull failed. There might be conflicts or no upstream set."
764
+ exit 1
765
+ fi
766
+ exit 0
774
767
  fi
775
768
 
776
769
  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
770
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
771
+ print_error "Not a git repository!"
772
+ exit 1
773
+ fi
774
+ pretty_git_log
775
+ exit 0
783
776
  fi
784
777
 
785
778
  if [[ "$CLI_MODE" == "architect" ]]; then
786
- summon_gitignore
787
- exit 0
779
+ summon_gitignore
780
+ exit 0
788
781
  fi
789
782
 
790
783
  # Mode: Time Turner (Undo)
791
784
  if [[ "$CLI_MODE" == "undo" ]]; then
792
- print_status "󱦟 Spinning the Time Turner..."
785
+ print_status "󱦟 Spinning the Time Turner..."
793
786
 
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
787
+ # Check if there is a commit to undo
788
+ if ! git rev-parse HEAD >/dev/null 2>&1; then
789
+ print_error "No commits to undo! This repo is empty."
790
+ exit 1
791
+ fi
792
+
793
+ # Check if we are on the initial commit (no parent)
794
+ is_initial_commit=false
795
+ if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
796
+ is_initial_commit=true
797
+ fi
798
+
799
+ # Ask user for the type of undo
800
+ undo_type=$(gum choose --header "Select Undo Level:" \
801
+ "Soft (Undo commit, keep changes staged - Best for fixing typos)" \
802
+ "Mixed (Undo commit, keep changes unstaged - Best for splitting work)" \
803
+ "Hard (DESTROY changes - Revert to previous state)" \
804
+ "Cancel")
805
+
806
+ case "$undo_type" in
807
+ "Soft"*)
808
+ if [[ "$is_initial_commit" == "true" ]]; then
809
+ git update-ref -d HEAD
810
+ else
811
+ git reset --soft HEAD~1
798
812
  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
813
+ print_status "✨ Success! I undid the commit, but kept your files ready to commit again."
814
+ ;;
815
+ "Mixed"*)
816
+ if [[ "$is_initial_commit" == "true" ]]; then
817
+ git update-ref -d HEAD
818
+ git rm --cached -r -q .
819
+ else
820
+ git reset HEAD~1
804
821
  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
822
+ print_status "󱞈 Success! I undid the commit and unstaged the files."
823
+ ;;
824
+ "Hard"*)
825
+ if gum confirm " DANGER: This deletes your work forever. Are you sure?"; then
826
+ if [[ "$is_initial_commit" == "true" ]]; then
827
+ # Unstage everything first, then clean to ensure files are deleted
828
+ git update-ref -d HEAD
829
+ git rm --cached -r -q . >/dev/null 2>&1
830
+ git clean -fd
831
+ else
832
+ git reset --hard HEAD~1
833
+ fi
834
+ print_status "󱠇 Obliviate! The last commit and its changes are destroyed."
835
+ else
836
+ print_status "Operation cancelled."
837
+ fi
838
+ ;;
839
+ *)
840
+ print_status "Mischief managed (Cancelled)."
841
+ ;;
842
+ esac
843
+ exit 0
851
844
  fi
852
845
 
853
846
  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
847
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
848
+ print_error "Not a git repository!"
849
+ exit 1
850
+ fi
858
851
 
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
852
+ if [[ -z "$CLI_MSG" ]]; then
853
+ print_error "Error: Commit message required for Lazy Wizard (-z)"
854
+ echo "Usage: bimagic -z \"commit message\""
855
+ exit 1
856
+ fi
864
857
 
865
- print_status " Lazy Wizard invoked!"
858
+ print_status " Lazy Wizard invoked!"
866
859
 
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
860
+ # 1. Add all changes
861
+ if gum spin --title "Adding files..." -- git add .; then
862
+ print_status "Files added."
863
+ else
864
+ print_error "Failed to add files."
865
+ exit 1
866
+ fi
874
867
 
875
- # 2. Commit
876
- if git commit -m "$CLI_MSG"; then
877
- print_status "Committed: $CLI_MSG"
868
+ # 2. Commit
869
+ if git commit -m "$CLI_MSG"; then
870
+ print_status "Committed: $CLI_MSG"
871
+ else
872
+ print_error "Commit failed (nothing to commit?)"
873
+ exit 1
874
+ fi
875
+
876
+ # 3. Push
877
+ branch=$(get_current_branch)
878
+ print_status "Pushing to $branch..."
879
+
880
+ if gum spin --title "Pushing..." -- git push; then
881
+ print_status "󱝂 Magic complete!"
882
+ else
883
+ # Try setting upstream if standard push failed
884
+ print_warning "Standard push failed. Trying to set upstream..."
885
+ if gum spin --title "Pushing (upstream)..." -- git push -u origin "$branch"; then
886
+ print_status "󱝂 Magic complete (upstream set)!"
878
887
  else
879
- print_error "Commit failed (nothing to commit?)"
880
- exit 1
888
+ print_error "Push failed."
889
+ exit 1
881
890
  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
891
+ fi
892
+ exit 0
900
893
  fi
901
894
 
902
895
  CONFIG_DIR="$HOME/.config/bimagic"
903
896
  VERSION_FILE="$CONFIG_DIR/version"
904
897
 
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."
898
+ resurrect_commit() {
899
+ print_status "󰔪 Summoning the Resurrection Stone..."
900
+
901
+ # Check if reflog has entries
902
+ if [[ -z "$(git reflog 2>/dev/null)" ]]; then
903
+ print_error "The ancient logs are empty. Nothing to resurrect."
904
+ return 1
905
+ fi
906
+
907
+ echo "Search the ancient timelines for your lost code:"
908
+ # Use gum filter to search through the entire reflog
909
+ 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...")
910
+
911
+ if [[ -z "$selected_log" ]]; then
912
+ print_status "Resurrection cancelled."
913
+ return 0
914
+ fi
915
+
916
+ # Extract the hash (the first word)
917
+ local target_hash=$(echo "$selected_log" | awk '{print $1}')
918
+
919
+ echo
920
+ gum style --border rounded --border-foreground "$BIMAGIC_PRIMARY" --padding "1 2" \
921
+ "TARGET TIMELINE:" "$selected_log"
922
+ echo
923
+
924
+ # Give the user options on how to resurrect
925
+ local action=$(gum choose "󰔱 Create a new branch here (Safest)" " Hard Reset current branch to here (Dangerous)" "Cancel")
926
+
927
+ case "$action" in
928
+ "󰔱 Create a new branch here (Safest)")
929
+ local new_branch=$(gum input --placeholder "Enter new branch name (e.g., recovered-code)")
930
+ if [[ -n "$new_branch" ]]; then
931
+ git checkout -b "$new_branch" "$target_hash"
932
+ print_status "󱝁 Timeline restored! You are now on branch: $new_branch"
921
933
  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"
934
+ ;;
935
+ " Hard Reset current branch to here (Dangerous)")
936
+ if gum confirm "This will overwrite your CURRENT work. Are you absolutely sure?"; then
937
+ git reset --hard "$target_hash"
938
+ print_status " Timeline overwritten. Welcome back."
939
+ fi
940
+ ;;
941
+ *)
942
+ print_status "The stone goes dormant."
943
+ ;;
944
+ esac
945
+ }
927
946
 
928
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to open the spellbook..."
929
- read -r </dev/tty
947
+ show_welcome_banner() {
948
+ clear
949
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_1")▗▖ ▄ ▄▄▄▄ ▗▄▖ ▗▄▄▖▄ ▗▄▄▖\033[0m"
950
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_2")▐▌ ▄ █ █ █ ▐▌ ▐▌▐▌ ▄ ▐▌ \033[0m"
951
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_3")▐▛▀▚▖█ █ █ ▐▛▀▜▌▐▌▝▜▌█ ▐▌ \033[0m"
952
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_4")▐▙▄▞▘█ ▐▌ ▐▌▝▚▄▞▘█ ▝▚▄▄▖\033[0m"
953
+ echo -e "$(get_ansi_esc "$BANNER_COLOR_5") \033[0m"
954
+
955
+ echo
956
+ gum style --foreground "$BIMAGIC_PRIMARY" --bold "✨ Welcome to Bimagic Git Wizard $VERSION ✨"
957
+ echo
958
+
959
+ if [[ ! -f "$VERSION_FILE" ]]; then
960
+ gum style --foreground "$BIMAGIC_SUCCESS" "It looks like this is your first time using Bimagic! Let's cast some spells."
961
+ else
962
+ gum style --foreground "$BIMAGIC_SUCCESS" "Bimagic has been updated to $VERSION! Enjoy the new magic."
963
+ fi
964
+ echo
965
+
966
+ # Save the current version so this doesn't show again until the next update
967
+ mkdir -p "$CONFIG_DIR"
968
+ echo "$VERSION" >"$VERSION_FILE"
969
+
970
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to open the spellbook..."
971
+ read -r </dev/tty
930
972
  }
931
973
 
932
974
  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
975
+ # Check if the version file doesn't exist, OR if it doesn't match the current version
976
+ if [[ ! -f "$VERSION_FILE" ]] || [[ "$(cat "$VERSION_FILE")" != "$VERSION" ]]; then
977
+ show_welcome_banner
978
+ fi
937
979
  fi
938
980
 
939
981
  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"
982
+ clear
983
+ show_repo_status # <-- shows the status dashboard at the top
984
+ # gum menu
985
+ choice=$(
986
+ gum choose --header " Choose your spell: (j/k to navigate)" \
987
+ --cursor " " --cursor.foreground "$BIMAGIC_PRIMARY" \
988
+ " Clone repository" \
989
+ " Init new repo" \
990
+ " Add files" \
991
+ " Commit changes" \
992
+ " Push to remote" \
993
+ " Pull latest changes" \
994
+ " Create/switch branch" \
995
+ " Set remote" \
996
+ "󱖫 Show status" \
997
+ " Contributor Statistics" \
998
+ " Git graph" \
999
+ "󰓗 Summon the Architect (.gitignore)" \
1000
+ "󰮘 Remove files/folders (rm)" \
1001
+ " Merge branches" \
1002
+ " Uninitialize repo" \
1003
+ "󰔪 Summon the Resurrection Stone (Recover lost code)" \
1004
+ "󰁯 Revert commit(s)" \
1005
+ "󰓗 Stash operations" \
1006
+ "󰿅 Exit"
1007
+ )
1008
+ echo
1009
+
1010
+ case "$choice" in
1011
+ " Clone repository")
1012
+ repo_url=$(gum input --placeholder "Enter repository URL")
1013
+ if [[ -z "$repo_url" ]]; then continue; fi
1014
+
1015
+ repo_depth=$(gum input --placeholder "Enter depth (empty for full clone)")
1016
+
1017
+ clone_mode=$(gum choose "Standard Clone" "Interactive (Select files)")
1018
+
1019
+ if [[ "$clone_mode" == "Interactive (Select files)" ]]; then
1020
+ clone_repo "$repo_url" "true" "$repo_depth"
1021
+ else
1022
+ clone_repo "$repo_url" "false" "$repo_depth"
1023
+ fi
1024
+ ;;
1025
+ "󰓗 Stash operations")
1026
+ while true; do
1027
+ stash_choice=$(gum choose --header "Stash Operations" \
1028
+ "󱉛 Push (Save) changes" \
1029
+ "󱉙 Pop latest stash" \
1030
+ " List stashes" \
1031
+ " Apply specific stash" \
1032
+ " Drop specific stash" \
1033
+ "󰎟 Clear all stashes" \
1034
+ "󰌍 Back")
1035
+
1036
+ case "$stash_choice" in
1037
+ "󱉛 Push (Save) changes")
1038
+ msg=$(gum input --placeholder "Optional stash message")
1039
+ # Ask about untracked files
1040
+ if gum confirm "Include untracked files?"; then
1041
+ include_untracked="-u"
978
1042
  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
1043
+ include_untracked=""
1087
1044
  fi
1088
1045
 
1089
- if [[ "$dirname" == "." ]]; then
1090
- git init
1091
- print_status "Repo initialized in current directory: $(pwd)"
1046
+ if git stash push $include_untracked -m "$msg"; then
1047
+ print_status "Changes stashed successfully!"
1092
1048
  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"
1049
+ print_error "Failed to stash changes."
1105
1050
  fi
1106
1051
  ;;
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."
1052
+ "󱉙 Pop latest stash")
1053
+ if git stash pop; then
1054
+ print_status "Stash popped successfully!"
1116
1055
  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
1056
+ print_error "Failed to pop stash (possible conflicts)."
1131
1057
  fi
1132
1058
  ;;
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
1059
+ " List stashes")
1060
+ if [[ -z "$(git stash list)" ]]; then
1061
+ print_warning "No stashes found."
1138
1062
  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
1063
+ git stash list | gum style --border normal --padding "0 1"
1151
1064
  fi
1152
1065
  ;;
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
1066
+ " Apply specific stash")
1067
+ if [[ -z "$(git stash list)" ]]; then
1068
+ print_warning "No stashes found."
1069
+ continue
1173
1070
  fi
1174
1071
 
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."
1072
+ stash_entry=$(git stash list | gum filter --placeholder "Select stash to apply")
1073
+ if [[ -n "$stash_entry" ]]; then
1074
+ stash_id=$(echo "$stash_entry" | cut -d: -f1)
1075
+ if git stash apply "$stash_id"; then
1076
+ print_status "Applied $stash_id"
1077
+ else
1078
+ print_error "Failed to apply $stash_id"
1079
+ fi
1182
1080
  fi
1183
1081
  ;;
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."
1082
+ " Drop specific stash")
1083
+ if [[ -z "$(git stash list)" ]]; then
1084
+ print_warning "No stashes found."
1085
+ continue
1190
1086
  fi
1191
1087
 
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"
1088
+ stash_entry=$(git stash list | gum filter --placeholder "Select stash to drop")
1089
+ if [[ -n "$stash_entry" ]]; then
1090
+ stash_id=$(echo "$stash_entry" | cut -d: -f1)
1091
+ if gum confirm "Are you sure you want to drop $stash_id?"; then
1092
+ if git stash drop "$stash_id"; then
1093
+ print_status "Dropped $stash_id"
1252
1094
  else
1253
- print_warning "No branch selected."
1095
+ print_error "Failed to drop $stash_id"
1254
1096
  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
1097
+ fi
1098
+ fi
1275
1099
  ;;
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
1100
+ "󰎟 Clear all stashes")
1101
+ if [[ -z "$(git stash list)" ]]; then
1102
+ print_warning "No stashes found."
1103
+ continue
1284
1104
  fi
1285
1105
 
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."
1106
+ if gum confirm "DANGER: This will delete ALL stashes. Continue?"; then
1107
+ if git stash clear; then
1108
+ print_status "All stashes cleared."
1109
+ else
1110
+ print_error "Failed to clear stashes."
1111
+ fi
1308
1112
  else
1309
- print_status "Operation cancelled."
1113
+ print_status "Operation cancelled."
1310
1114
  fi
1311
1115
  ;;
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
1116
+ "󰌍 Back")
1117
+ break
1327
1118
  ;;
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
1119
+ esac
1120
+ echo
1121
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1122
+ read -r </dev/tty
1123
+ done
1124
+ ;;
1125
+ " Init new repo")
1126
+ dirname=$(gum input --placeholder "Enter repo directory name (or '.' for current dir)")
1127
+ if [[ -z "$dirname" ]]; then
1128
+ print_warning "Operation cancelled."
1129
+ continue
1130
+ fi
1131
+
1132
+ if [[ "$dirname" == "." ]]; then
1133
+ git init
1134
+ print_status "Repo initialized in current directory: $(pwd)"
1135
+ else
1136
+ mkdir -p "$dirname"
1137
+ # Run init and rename in a subshell to avoid directory tracking issues
1138
+ (
1139
+ cd "$dirname" || exit 1
1140
+ git init
1141
+ current_branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1142
+ if [[ "$current_branch" == "master" ]]; then
1143
+ git branch -M main
1144
+ echo "Default branch renamed from 'master' to 'main' in $dirname"
1145
+ fi
1146
+ )
1147
+ print_status "Repo initialized in new directory: $dirname"
1148
+ fi
1149
+ ;;
1150
+ " Add files")
1151
+ # Show untracked + modified files, plus an [ALL] option
1152
+ files=$( (
1153
+ echo "[ALL]"
1154
+ git ls-files --others --modified --exclude-standard
1155
+ ) | gum filter --no-limit --placeholder "Select files to add")
1156
+
1157
+ if [[ -z "$files" ]]; then
1158
+ print_warning "No files selected."
1159
+ else
1160
+ # gum filter returns selected items separated by newlines.
1161
+ # We need to handle the case where [ALL] is selected along with other files.
1162
+ if echo "$files" | grep -q "\[ALL\]"; then
1163
+ git add .
1164
+ print_status "All files staged."
1165
+ else
1166
+ # Correctly handle filenames with spaces by reading line by line
1167
+ echo "$files" | while read -r f; do
1168
+ [[ -n "$f" ]] && git add "$f"
1169
+ done
1170
+ print_status "Selected files staged."
1171
+ # Optionally, list the files that were staged
1172
+ echo "$files"
1173
+ fi
1174
+ fi
1175
+ ;;
1176
+ " Commit changes")
1177
+ commit_mode=$(gum choose "󰦥 Magic Commit (Builder)" "󱐋 Quick Commit (One-line)")
1178
+
1179
+ if [[ "$commit_mode" == "󰦥 Magic Commit (Builder)" ]]; then
1180
+ commit_wizard
1181
+ else
1182
+ msg=$(gum input --placeholder "Enter commit message")
1183
+ if [[ -z "$msg" ]]; then
1184
+ print_warning "No commit message provided. Cancelled."
1185
+ continue
1186
+ fi
1187
+
1188
+ if gum confirm "Commit changes?"; then
1189
+ git commit -m "$msg"
1190
+ print_status "Commit done!"
1191
+ else
1192
+ print_status "Commit cancelled."
1193
+ fi
1194
+ fi
1195
+ ;;
1196
+ " Push to remote")
1197
+ branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1198
+ branch=${branch:-main}
1199
+
1200
+ remotes=$(git remote)
1201
+
1202
+ if [[ -z "$remotes" ]]; then
1203
+ print_error "No remote set!"
1204
+ if setup_remote "origin"; then
1205
+ remote="origin"
1206
+ else
1207
+ continue
1208
+ fi
1209
+ else
1210
+ # Select remote if multiple exist
1211
+ if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1212
+ remote="$remotes"
1213
+ else
1214
+ remote=$(echo "$remotes" | gum filter --placeholder "Select remote to push to")
1215
+ fi
1216
+ fi
1217
+
1218
+ [[ -z "$remote" ]] && continue
1219
+
1220
+ if gum confirm "Push branch '$branch' to '$remote'?"; then
1221
+ echo "Pushing branch '$branch' to '$remote'..."
1222
+ gum spin --title "Pushing..." -- git push -u "$remote" "$branch"
1223
+ else
1224
+ print_status "Push cancelled."
1225
+ fi
1226
+ ;;
1227
+ " Pull latest changes")
1228
+ # Auto-fetch before pulling
1229
+ if gum spin --title "Fetching updates..." -- git fetch --all; then
1230
+ print_status "Fetch complete."
1231
+ else
1232
+ print_warning "Fetch encountered issues."
1233
+ fi
1234
+
1235
+ pull_choice=$(gum choose --header "Select pull mode" \
1236
+ "Pull specific branch" \
1237
+ "Pull all")
1238
+
1239
+ case "$pull_choice" in
1240
+ "Pull all")
1241
+ if gum confirm "Run 'git pull --all'?"; then
1242
+ gum spin --title "Pulling all..." -- git pull --all
1243
+ print_status "Pull all complete."
1244
+ else
1245
+ print_status "Pull cancelled."
1246
+ fi
1247
+ ;;
1248
+ "Pull specific branch")
1249
+ branch=$(gum input --value "main" --placeholder "Enter branch to pull")
1250
+ branch=${branch:-main}
1251
+
1252
+ remotes=$(git remote 2>/dev/null)
1253
+ if [[ -z "$remotes" ]]; then
1254
+ print_error "No remote set! Cannot pull."
1255
+ else
1256
+ # Select remote if multiple exist
1257
+ if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1258
+ remote="$remotes"
1332
1259
  else
1333
- continue
1260
+ remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
1334
1261
  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."
1262
+
1263
+ if [[ -n "$remote" ]]; then
1264
+ if gum confirm "Pull branch '$branch' from '$remote'?"; then
1265
+ gum spin --title "Pulling..." -- git pull "$remote" "$branch"
1266
+ else
1267
+ print_status "Pull cancelled."
1268
+ fi
1348
1269
  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
1270
+ print_warning "No remote selected."
1359
1271
  fi
1360
- ;;
1361
- " Contributor Statistics")
1362
- show_contributor_stats
1363
- ;;
1272
+ fi
1273
+ ;;
1274
+ esac
1275
+ ;;
1276
+ " Create/switch branch")
1277
+ # Show current branch and all available branches
1278
+ current_branch=$(get_current_branch)
1279
+ print_status "Current branch: $current_branch"
1280
+ echo
1281
+ print_status "Available branches:"
1282
+ show_branches
1283
+ echo
1364
1284
 
1365
- "󰓗 Summon the Architect (.gitignore)")
1366
- summon_gitignore
1367
- ;;
1285
+ # Let user choose between existing branches or new branch
1286
+ branch_option=$(gum choose "Switch to existing branch" "Create new branch")
1287
+
1288
+ case "$branch_option" in
1289
+ "Switch to existing branch")
1290
+ # Use gum filter to select from existing branches
1291
+ existing_branch=$(git branch --format='%(refname:short)' | gum filter --placeholder "Select branch to switch to")
1292
+ if [[ -n "$existing_branch" ]]; then
1293
+ git checkout "$existing_branch"
1294
+ print_status "Switched to branch: $existing_branch"
1295
+ else
1296
+ print_warning "No branch selected."
1297
+ fi
1298
+ ;;
1299
+ "Create new branch")
1300
+ new_branch=$(gum input --placeholder "Enter new branch name")
1301
+ if [[ -n "$new_branch" ]]; then
1302
+ git checkout -b "$new_branch"
1303
+ print_status "Created and switched to new branch: $new_branch"
1304
+ else
1305
+ print_error "No branch name provided."
1306
+ fi
1307
+ ;;
1308
+ *)
1309
+ print_warning "Operation cancelled."
1310
+ ;;
1311
+ esac
1312
+ ;;
1313
+ " Set remote")
1314
+ setup_remote "origin"
1315
+ ;;
1316
+ "󱖫 Show status")
1317
+ git status
1318
+ ;;
1319
+ "󰮘 Remove files/folders (rm)")
1320
+ # List tracked + untracked files for removal
1321
+ files=$(git ls-files --cached --others --exclude-standard |
1322
+ gum filter --no-limit --placeholder "Select files/folders to remove")
1323
+
1324
+ if [[ -z "$files" ]]; then
1325
+ print_warning "No files selected."
1326
+ continue # Use continue to go back to the main menu
1327
+ fi
1368
1328
 
1369
- "󰁯 Revert commit(s)")
1370
- print_status "Fetching commit history..."
1371
- echo
1329
+ echo "Files selected for removal:"
1330
+ # Use 'tput' for better visual separation and color
1331
+ tput setaf 3
1332
+ echo "$files"
1333
+ tput sgr0
1334
+ echo
1372
1335
 
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}')
1336
+ if gum confirm "Confirm removal? This cannot be undone."; then
1337
+ # Use a 'while read' loop to correctly handle filenames with spaces
1338
+ echo "$files" | while read -r f; do
1339
+ if [[ -z "$f" ]]; then continue; fi # Skip empty lines
1377
1340
 
1378
- if [[ -z "$commits" ]]; then
1379
- print_warning "No commit selected. Revert cancelled."
1380
- continue
1341
+ # Check if the file is tracked by git
1342
+ if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
1343
+ # It's a tracked file, use 'git rm'
1344
+ git rm -rf "$f"
1345
+ else
1346
+ # It's an untracked file, use 'rm'
1347
+ rm -rf "$f"
1381
1348
  fi
1349
+ done
1350
+ print_status "Selected files/folders have been removed."
1351
+ else
1352
+ print_status "Operation cancelled."
1353
+ fi
1354
+ ;;
1355
+ " Uninitialize repo")
1356
+ print_warning "This will completely uninitialize the Git repository in this folder."
1357
+ echo "This action will delete the .git directory and cannot be undone!"
1358
+ echo
1382
1359
 
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
1360
+ if gum confirm "Are you sure you want to continue?"; then
1361
+ if [ -d ".git" ]; then
1362
+ rm -rf .git
1363
+ print_status "Git repository has been uninitialized."
1364
+ else
1365
+ print_error "No .git directory found here. Nothing to do."
1366
+ fi
1367
+ else
1368
+ print_status "Operation cancelled."
1369
+ fi
1370
+ ;;
1371
+ "󰿅 Exit")
1372
+ if gum confirm "Are you sure you want to exit?"; then
1373
+ echo "Git Wizard vanishes in a puff of smoke..."
1374
+ exit 0
1375
+ else
1376
+ continue
1377
+ fi
1378
+ ;;
1379
+ " Merge branches")
1380
+ current_branch=$(get_current_branch)
1381
+ print_status "You are on branch: $current_branch"
1382
+ echo
1383
+
1384
+ # Pick branch to merge from
1385
+ merge_branch=$(git branch --format='%(refname:short)' |
1386
+ grep -v "^$current_branch$" |
1387
+ gum filter --placeholder "Select branch to merge into $current_branch")
1388
+
1389
+ if [[ -z "$merge_branch" ]]; then
1390
+ print_warning "No branch selected. Merge cancelled."
1391
+ else
1392
+ if gum confirm "Merge branch '$merge_branch' into '$current_branch'?"; then
1393
+ echo "Merging branch '$merge_branch' into '$current_branch'..."
1394
+ if gum spin --title "Merging..." -- git merge "$merge_branch"; then
1395
+ print_status "Merge successful!"
1399
1396
  else
1400
- print_status "Revert cancelled."
1397
+ print_error "Merge had conflicts! Resolve them manually."
1401
1398
  fi
1402
- ;;
1399
+ else
1400
+ print_status "Merge cancelled."
1401
+ fi
1402
+ fi
1403
+ ;;
1404
+ " Contributor Statistics")
1405
+ show_contributor_stats
1406
+ ;;
1403
1407
 
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
- ;;
1408
+ "󰓗 Summon the Architect (.gitignore)")
1409
+ summon_gitignore
1410
+ ;;
1421
1411
 
1422
- *)
1423
- print_error "Invalid choice! Try again."
1424
- echo "Git Wizard vanishes in a puff of smoke..."
1425
- break
1426
- ;;
1427
- esac
1412
+ "󰔪 Summon the Resurrection Stone (Recover lost code)")
1413
+ resurrect_commit
1414
+ ;;
1415
+
1416
+ "󰁯 Revert commit(s)")
1417
+ print_status "Fetching commit history..."
1418
+ echo
1428
1419
 
1420
+ # Show commits as "<hash> <message>" but keep hash separately
1421
+ commits=$(git log --oneline --decorate |
1422
+ gum filter --no-limit --placeholder "Select commit(s) to revert" |
1423
+ awk '{print $1}')
1424
+
1425
+ if [[ -z "$commits" ]]; then
1426
+ print_warning "No commit selected. Revert cancelled."
1427
+ continue
1428
+ fi
1429
+
1430
+ echo "You selected:"
1431
+ echo "$commits"
1429
1432
  echo
1430
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1431
- read -r </dev/tty
1433
+
1434
+ if gum confirm "Confirm revert?"; then
1435
+ for c in $commits; do
1436
+ echo "Reverting commit $c..."
1437
+ if git revert --no-edit "$c"; then
1438
+ print_status "Commit $c reverted."
1439
+ else
1440
+ print_error "Conflict occurred while reverting $c!"
1441
+ echo "Please resolve conflicts, then run:"
1442
+ echo " git revert --continue"
1443
+ break
1444
+ fi
1445
+ done
1446
+ else
1447
+ print_status "Revert cancelled."
1448
+ fi
1449
+ ;;
1450
+
1451
+ " Git graph")
1452
+ color=${YELLOW}
1453
+ line1="Git graph"
1454
+ line2="[INFO] press 'q' to exit"
1455
+
1456
+ echo -e "${color}╭$(printf '─%.0s' $(seq 1 30))╮${NC}"
1457
+ printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line1"
1458
+ printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line2"
1459
+ echo -e "${color}╰$(printf '─%.0s' $(seq 1 30))╯${NC}"
1460
+
1461
+ # Use gum spin while loading the graph.
1462
+ # Since git log can be long, we pipe it to less if it's too big,
1463
+ # but gum spin doesn't work well with interactive pagers.
1464
+ # However, for a simple "pretty log", we can just run it.
1465
+ gum spin --title "Drawing git graph..." -- sleep 2
1466
+ pretty_git_log
1467
+ ;;
1468
+
1469
+ *)
1470
+ print_error "Invalid choice! Try again."
1471
+ echo "Git Wizard vanishes in a puff of smoke..."
1472
+ break
1473
+ ;;
1474
+ esac
1475
+
1476
+ echo
1477
+ gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1478
+ read -r </dev/tty
1432
1479
  done