bimagic 1.4.5 → 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 +29 -5
  2. package/bimagic +1209 -1129
  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.5"
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)
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"
212
223
  else
213
- ahead=0
214
- behind=0
224
+ status="🟢 clean"
225
+ color="$BIMAGIC_SUCCESS"
215
226
  fi
227
+ else
228
+ status="🟡 uncommitted"
229
+ color="$BIMAGIC_WARNING"
230
+ fi
216
231
 
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
227
- else
228
- status="🟡 uncommitted"
229
- color="$BIMAGIC_WARNING"
230
- fi
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
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
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
575
554
  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,722 +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
- -a | --architect)
717
- CLI_MODE="architect"
718
- shift
719
- ;;
720
- *)
721
- # Argument handling based on current mode
722
- if [[ "$CLI_MODE" == "clone" && -z "$CLI_URL" ]]; then
723
- CLI_URL="$1"
724
- elif [[ "$CLI_MODE" == "lazy" && -z "$CLI_MSG" ]]; then
725
- CLI_MSG="$1"
726
- fi
727
- shift
728
- ;;
729
- 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
730
734
  done
731
735
 
732
736
  # --- Mode Handlers ---
733
737
 
734
738
  if [[ "$CLI_MODE" == "clone" ]]; then
735
- if [[ -z "$CLI_URL" ]]; then
736
- print_error "Error: Repository URL required with -d"
737
- exit 1
738
- fi
739
- clone_repo "$CLI_URL" "$CLI_INTERACTIVE" "$CLI_DEPTH"
740
- 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
741
745
  fi
742
746
 
743
-
744
747
  if [[ "$CLI_MODE" == "status" ]]; then
745
- show_repo_status
746
- exit 0
748
+ show_repo_status
749
+ exit 0
750
+ fi
751
+
752
+ if [[ "$CLI_MODE" == "pull" ]]; then
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
747
773
  fi
748
774
 
749
775
  if [[ "$CLI_MODE" == "graph" ]]; then
750
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
751
- print_error "Not a git repository!"
752
- exit 1
753
- fi
754
- pretty_git_log
755
- 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
756
782
  fi
757
783
 
758
784
  if [[ "$CLI_MODE" == "architect" ]]; then
759
- summon_gitignore
760
- exit 0
785
+ summon_gitignore
786
+ exit 0
761
787
  fi
762
788
 
763
789
  # Mode: Time Turner (Undo)
764
790
  if [[ "$CLI_MODE" == "undo" ]]; then
765
- print_status "󱦟 Spinning the Time Turner..."
791
+ print_status "󱦟 Spinning the Time Turner..."
766
792
 
767
- # Check if there is a commit to undo
768
- if ! git rev-parse HEAD >/dev/null 2>&1; then
769
- print_error "No commits to undo! This repo is empty."
770
- 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
771
818
  fi
772
-
773
- # Check if we are on the initial commit (no parent)
774
- is_initial_commit=false
775
- if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
776
- 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
777
827
  fi
778
-
779
- # Ask user for the type of undo
780
- undo_type=$(gum choose --header "Select Undo Level:" \
781
- "Soft (Undo commit, keep changes staged - Best for fixing typos)" \
782
- "Mixed (Undo commit, keep changes unstaged - Best for splitting work)" \
783
- "Hard (DESTROY changes - Revert to previous state)" \
784
- "Cancel")
785
-
786
- case "$undo_type" in
787
- "Soft"*)
788
- if [[ "$is_initial_commit" == "true" ]]; then
789
- git update-ref -d HEAD
790
- else
791
- git reset --soft HEAD~1
792
- fi
793
- print_status "✨ Success! I undid the commit, but kept your files ready to commit again."
794
- ;;
795
- "Mixed"*)
796
- if [[ "$is_initial_commit" == "true" ]]; then
797
- git update-ref -d HEAD
798
- git rm --cached -r -q .
799
- else
800
- git reset HEAD~1
801
- fi
802
- print_status "󱞈 Success! I undid the commit and unstaged the files."
803
- ;;
804
- "Hard"*)
805
- if gum confirm " DANGER: This deletes your work forever. Are you sure?"; then
806
- if [[ "$is_initial_commit" == "true" ]]; then
807
- # Unstage everything first, then clean to ensure files are deleted
808
- git update-ref -d HEAD
809
- git rm --cached -r -q . >/dev/null 2>&1
810
- git clean -fd
811
- else
812
- git reset --hard HEAD~1
813
- fi
814
- print_status "󱠇 Obliviate! The last commit and its changes are destroyed."
815
- else
816
- print_status "Operation cancelled."
817
- fi
818
- ;;
819
- *)
820
- print_status "Mischief managed (Cancelled)."
821
- ;;
822
- esac
823
- 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
824
850
  fi
825
851
 
826
852
  if [[ "$CLI_MODE" == "lazy" ]]; then
827
- if ! git rev-parse --git-dir >/dev/null 2>&1; then
828
- print_error "Not a git repository!"
829
- exit 1
830
- fi
831
-
832
- if [[ -z "$CLI_MSG" ]]; then
833
- print_error "Error: Commit message required for Lazy Wizard (-z)"
834
- echo "Usage: bimagic -z \"commit message\""
835
- exit 1
836
- fi
837
-
838
- print_status " Lazy Wizard invoked!"
853
+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
854
+ print_error "Not a git repository!"
855
+ exit 1
856
+ fi
839
857
 
840
- # 1. Add all changes
841
- if gum spin --title "Adding files..." -- git add .; then
842
- print_status "Files added."
843
- else
844
- print_error "Failed to add files."
845
- exit 1
846
- 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
847
863
 
848
- # 2. Commit
849
- if git commit -m "$CLI_MSG"; then
850
- print_status "Committed: $CLI_MSG"
851
- else
852
- print_error "Commit failed (nothing to commit?)"
853
- exit 1
854
- fi
864
+ print_status " Lazy Wizard invoked!"
855
865
 
856
- # 3. Push
857
- branch=$(get_current_branch)
858
- print_status "Pushing to $branch..."
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
859
873
 
860
- if gum spin --title "Pushing..." -- git push; then
861
- print_status "󱝂 Magic complete!"
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)!"
862
893
  else
863
- # Try setting upstream if standard push failed
864
- print_warning "Standard push failed. Trying to set upstream..."
865
- if gum spin --title "Pushing (upstream)..." -- git push -u origin "$branch"; then
866
- print_status "󱝂 Magic complete (upstream set)!"
867
- else
868
- print_error "Push failed."
869
- exit 1
870
- fi
894
+ print_error "Push failed."
895
+ exit 1
871
896
  fi
872
- exit 0
897
+ fi
898
+ exit 0
873
899
  fi
874
900
 
875
901
  CONFIG_DIR="$HOME/.config/bimagic"
876
902
  VERSION_FILE="$CONFIG_DIR/version"
877
903
 
878
- show_welcome_banner() {
879
- clear
880
- echo -e "$(get_ansi_esc "$BANNER_COLOR_1")▗▖ ▄ ▄▄▄▄ ▗▄▖ ▗▄▄▖▄ ▗▄▄▖\033[0m"
881
- echo -e "$(get_ansi_esc "$BANNER_COLOR_2")▐▌ ▄ █ █ ▐▌ ▐▌▐▌ ▄ ▐▌ \033[0m"
882
- echo -e "$(get_ansi_esc "$BANNER_COLOR_3")▐▛▀▚▖█ █ █ ▐▛▀▜▌▐▌▝▜▌█ ▐▌ \033[0m"
883
- echo -e "$(get_ansi_esc "$BANNER_COLOR_4")▐▙▄▞▘█ ▐▌ ▐▌▝▚▄▞▘█ ▝▚▄▄▖\033[0m"
884
- echo -e "$(get_ansi_esc "$BANNER_COLOR_5") \033[0m"
885
-
886
- echo
887
- gum style --foreground "$BIMAGIC_PRIMARY" --bold "✨ Welcome to Bimagic Git Wizard $VERSION ✨"
888
- echo
889
-
890
- if [[ ! -f "$VERSION_FILE" ]]; then
891
- gum style --foreground "$BIMAGIC_SUCCESS" "It looks like this is your first time using Bimagic! Let's cast some spells."
892
- else
893
- 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"
894
939
  fi
895
- echo
896
-
897
- # Save the current version so this doesn't show again until the next update
898
- mkdir -p "$CONFIG_DIR"
899
- 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
+ }
900
952
 
901
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to open the spellbook..."
902
- 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
903
978
  }
904
979
 
905
980
  if [[ -z "$CLI_MODE" ]]; then
906
- # Check if the version file doesn't exist, OR if it doesn't match the current version
907
- if [[ ! -f "$VERSION_FILE" ]] || [[ "$(cat "$VERSION_FILE")" != "$VERSION" ]]; then
908
- show_welcome_banner
909
- 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
910
985
  fi
911
986
 
912
987
  while true; do
913
- clear
914
- show_repo_status # <-- shows the status dashboard at the top
915
- # gum menu
916
- choice=$(
917
- gum choose --header " Choose your spell: (j/k to navigate)" \
918
- --cursor " " --cursor.foreground "$BIMAGIC_PRIMARY" \
919
- " Clone repository" \
920
- " Init new repo" \
921
- " Add files" \
922
- " Commit changes" \
923
- " Push to remote" \
924
- " Pull latest changes" \
925
- " Create/switch branch" \
926
- " Set remote" \
927
- "󱖫 Show status" \
928
- " Contributor Statistics" \
929
- " Git graph" \
930
- "󰓗 Summon the Architect (.gitignore)" \
931
- "󰮘 Remove files/folders (rm)" \
932
- " Merge branches" \
933
- " Uninitialize repo" \
934
- "󰁯 Revert commit(s)" \
935
- "󰓗 Stash operations" \
936
- "󰿅 Exit"
937
- )
938
- echo
939
-
940
- case "$choice" in
941
- " Clone repository")
942
- repo_url=$(gum input --placeholder "Enter repository URL")
943
- if [[ -z "$repo_url" ]]; then continue; fi
944
-
945
- repo_depth=$(gum input --placeholder "Enter depth (empty for full clone)")
946
-
947
- clone_mode=$(gum choose "Standard Clone" "Interactive (Select files)")
948
-
949
- if [[ "$clone_mode" == "Interactive (Select files)" ]]; then
950
- 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"
951
1048
  else
952
- clone_repo "$repo_url" "false" "$repo_depth"
953
- fi
954
- ;;
955
- "󰓗 Stash operations")
956
- while true; do
957
- stash_choice=$(gum choose --header "Stash Operations" \
958
- "󱉛 Push (Save) changes" \
959
- "󱉙 Pop latest stash" \
960
- " List stashes" \
961
- " Apply specific stash" \
962
- " Drop specific stash" \
963
- "󰎟 Clear all stashes" \
964
- "󰌍 Back")
965
-
966
- case "$stash_choice" in
967
- "󱉛 Push (Save) changes")
968
- msg=$(gum input --placeholder "Optional stash message")
969
- # Ask about untracked files
970
- if gum confirm "Include untracked files?"; then
971
- include_untracked="-u"
972
- else
973
- include_untracked=""
974
- fi
975
-
976
- if git stash push $include_untracked -m "$msg"; then
977
- print_status "Changes stashed successfully!"
978
- else
979
- print_error "Failed to stash changes."
980
- fi
981
- ;;
982
- "󱉙 Pop latest stash")
983
- if git stash pop; then
984
- print_status "Stash popped successfully!"
985
- else
986
- print_error "Failed to pop stash (possible conflicts)."
987
- fi
988
- ;;
989
- " List stashes")
990
- if [[ -z "$(git stash list)" ]]; then
991
- print_warning "No stashes found."
992
- else
993
- git stash list | gum style --border normal --padding "0 1"
994
- fi
995
- ;;
996
- " Apply specific stash")
997
- if [[ -z "$(git stash list)" ]]; then
998
- print_warning "No stashes found."
999
- continue
1000
- fi
1001
-
1002
- stash_entry=$(git stash list | gum filter --placeholder "Select stash to apply")
1003
- if [[ -n "$stash_entry" ]]; then
1004
- stash_id=$(echo "$stash_entry" | cut -d: -f1)
1005
- if git stash apply "$stash_id"; then
1006
- print_status "Applied $stash_id"
1007
- else
1008
- print_error "Failed to apply $stash_id"
1009
- fi
1010
- fi
1011
- ;;
1012
- " Drop specific stash")
1013
- if [[ -z "$(git stash list)" ]]; then
1014
- print_warning "No stashes found."
1015
- continue
1016
- fi
1017
-
1018
- stash_entry=$(git stash list | gum filter --placeholder "Select stash to drop")
1019
- if [[ -n "$stash_entry" ]]; then
1020
- stash_id=$(echo "$stash_entry" | cut -d: -f1)
1021
- if gum confirm "Are you sure you want to drop $stash_id?"; then
1022
- if git stash drop "$stash_id"; then
1023
- print_status "Dropped $stash_id"
1024
- else
1025
- print_error "Failed to drop $stash_id"
1026
- fi
1027
- fi
1028
- fi
1029
- ;;
1030
- "󰎟 Clear all stashes")
1031
- if [[ -z "$(git stash list)" ]]; then
1032
- print_warning "No stashes found."
1033
- continue
1034
- fi
1035
-
1036
- if gum confirm "DANGER: This will delete ALL stashes. Continue?"; then
1037
- if git stash clear; then
1038
- print_status "All stashes cleared."
1039
- else
1040
- print_error "Failed to clear stashes."
1041
- fi
1042
- else
1043
- print_status "Operation cancelled."
1044
- fi
1045
- ;;
1046
- "󰌍 Back")
1047
- break
1048
- ;;
1049
- esac
1050
- echo
1051
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1052
- read -r </dev/tty
1053
- done
1054
- ;;
1055
- " Init new repo")
1056
- dirname=$(gum input --placeholder "Enter repo directory name (or '.' for current dir)")
1057
- if [[ -z "$dirname" ]]; then
1058
- print_warning "Operation cancelled."
1059
- continue
1049
+ include_untracked=""
1060
1050
  fi
1061
1051
 
1062
- if [[ "$dirname" == "." ]]; then
1063
- git init
1064
- print_status "Repo initialized in current directory: $(pwd)"
1052
+ if git stash push $include_untracked -m "$msg"; then
1053
+ print_status "Changes stashed successfully!"
1065
1054
  else
1066
- mkdir -p "$dirname"
1067
- # Run init and rename in a subshell to avoid directory tracking issues
1068
- (
1069
- cd "$dirname" || exit 1
1070
- git init
1071
- current_branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1072
- if [[ "$current_branch" == "master" ]]; then
1073
- git branch -M main
1074
- echo "Default branch renamed from 'master' to 'main' in $dirname"
1075
- fi
1076
- )
1077
- print_status "Repo initialized in new directory: $dirname"
1055
+ print_error "Failed to stash changes."
1078
1056
  fi
1079
1057
  ;;
1080
- " Add files")
1081
- # Show untracked + modified files, plus an [ALL] option
1082
- files=$( (
1083
- echo "[ALL]"
1084
- git ls-files --others --modified --exclude-standard
1085
- ) | gum filter --no-limit --placeholder "Select files to add")
1086
-
1087
- if [[ -z "$files" ]]; then
1088
- print_warning "No files selected."
1058
+ "󱉙 Pop latest stash")
1059
+ if git stash pop; then
1060
+ print_status "Stash popped successfully!"
1089
1061
  else
1090
- # gum filter returns selected items separated by newlines.
1091
- # We need to handle the case where [ALL] is selected along with other files.
1092
- if echo "$files" | grep -q "\[ALL\]"; then
1093
- git add .
1094
- print_status "All files staged."
1095
- else
1096
- # Correctly handle filenames with spaces by reading line by line
1097
- echo "$files" | while read -r f; do
1098
- [[ -n "$f" ]] && git add "$f"
1099
- done
1100
- print_status "Selected files staged."
1101
- # Optionally, list the files that were staged
1102
- echo "$files"
1103
- fi
1062
+ print_error "Failed to pop stash (possible conflicts)."
1104
1063
  fi
1105
1064
  ;;
1106
- " Commit changes")
1107
- commit_mode=$(gum choose "󰦥 Magic Commit (Builder)" "󱐋 Quick Commit (One-line)")
1108
-
1109
- if [[ "$commit_mode" == "󰦥 Magic Commit (Builder)" ]]; then
1110
- commit_wizard
1065
+ " List stashes")
1066
+ if [[ -z "$(git stash list)" ]]; then
1067
+ print_warning "No stashes found."
1111
1068
  else
1112
- msg=$(gum input --placeholder "Enter commit message")
1113
- if [[ -z "$msg" ]]; then
1114
- print_warning "No commit message provided. Cancelled."
1115
- continue
1116
- fi
1117
-
1118
- if gum confirm "Commit changes?"; then
1119
- git commit -m "$msg"
1120
- print_status "Commit done!"
1121
- else
1122
- print_status "Commit cancelled."
1123
- fi
1069
+ git stash list | gum style --border normal --padding "0 1"
1124
1070
  fi
1125
1071
  ;;
1126
- " Push to remote")
1127
- branch=$(git symbolic-ref --short HEAD 2>/dev/null)
1128
- branch=${branch:-main}
1129
-
1130
- remotes=$(git remote)
1131
-
1132
- if [[ -z "$remotes" ]]; then
1133
- print_error "No remote set!"
1134
- if setup_remote "origin"; then
1135
- remote="origin"
1136
- else
1137
- continue
1138
- fi
1139
- else
1140
- # Select remote if multiple exist
1141
- if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1142
- remote="$remotes"
1143
- else
1144
- remote=$(echo "$remotes" | gum filter --placeholder "Select remote to push to")
1145
- fi
1072
+ " Apply specific stash")
1073
+ if [[ -z "$(git stash list)" ]]; then
1074
+ print_warning "No stashes found."
1075
+ continue
1146
1076
  fi
1147
1077
 
1148
- [[ -z "$remote" ]] && continue
1149
-
1150
- if gum confirm "Push branch '$branch' to '$remote'?"; then
1151
- echo "Pushing branch '$branch' to '$remote'..."
1152
- gum spin --title "Pushing..." -- git push -u "$remote" "$branch"
1153
- else
1154
- 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
1155
1086
  fi
1156
1087
  ;;
1157
- " Pull latest changes")
1158
- # Auto-fetch before pulling
1159
- if gum spin --title "Fetching updates..." -- git fetch --all; then
1160
- print_status "Fetch complete."
1161
- else
1162
- print_warning "Fetch encountered issues."
1088
+ " Drop specific stash")
1089
+ if [[ -z "$(git stash list)" ]]; then
1090
+ print_warning "No stashes found."
1091
+ continue
1163
1092
  fi
1164
1093
 
1165
- pull_choice=$(gum choose --header "Select pull mode" \
1166
- "Pull specific branch" \
1167
- "Pull all")
1168
-
1169
- case "$pull_choice" in
1170
- "Pull all")
1171
- if gum confirm "Run 'git pull --all'?"; then
1172
- gum spin --title "Pulling all..." -- git pull --all
1173
- print_status "Pull all complete."
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"
1174
1100
  else
1175
- print_status "Pull cancelled."
1101
+ print_error "Failed to drop $stash_id"
1176
1102
  fi
1177
- ;;
1178
- "Pull specific branch")
1179
- branch=$(gum input --value "main" --placeholder "Enter branch to pull")
1180
- branch=${branch:-main}
1181
-
1182
- remotes=$(git remote 2>/dev/null)
1183
- if [[ -z "$remotes" ]]; then
1184
- print_error "No remote set! Cannot pull."
1185
- else
1186
- # Select remote if multiple exist
1187
- if [[ $(echo "$remotes" | wc -l) -eq 1 ]]; then
1188
- remote="$remotes"
1189
- else
1190
- remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
1191
- fi
1192
-
1193
- if [[ -n "$remote" ]]; then
1194
- if gum confirm "Pull branch '$branch' from '$remote'?"; then
1195
- gum spin --title "Pulling..." -- git pull "$remote" "$branch"
1196
- else
1197
- print_status "Pull cancelled."
1198
- fi
1199
- else
1200
- print_warning "No remote selected."
1201
- fi
1202
- fi
1203
- ;;
1204
- esac
1205
- ;;
1206
- " Create/switch branch")
1207
- # Show current branch and all available branches
1208
- current_branch=$(get_current_branch)
1209
- print_status "Current branch: $current_branch"
1210
- echo
1211
- print_status "Available branches:"
1212
- show_branches
1213
- echo
1214
-
1215
- # Let user choose between existing branches or new branch
1216
- branch_option=$(gum choose "Switch to existing branch" "Create new branch")
1217
-
1218
- case "$branch_option" in
1219
- "Switch to existing branch")
1220
- # Use gum filter to select from existing branches
1221
- existing_branch=$(git branch --format='%(refname:short)' | gum filter --placeholder "Select branch to switch to")
1222
- if [[ -n "$existing_branch" ]]; then
1223
- git checkout "$existing_branch"
1224
- print_status "Switched to branch: $existing_branch"
1225
- else
1226
- print_warning "No branch selected."
1227
- fi
1228
- ;;
1229
- "Create new branch")
1230
- new_branch=$(gum input --placeholder "Enter new branch name")
1231
- if [[ -n "$new_branch" ]]; then
1232
- git checkout -b "$new_branch"
1233
- print_status "Created and switched to new branch: $new_branch"
1234
- else
1235
- print_error "No branch name provided."
1236
- fi
1237
- ;;
1238
- *)
1239
- print_warning "Operation cancelled."
1240
- ;;
1241
- esac
1242
- ;;
1243
- " Set remote")
1244
- setup_remote "origin"
1245
- ;;
1246
- "󱖫 Show status")
1247
- git status
1103
+ fi
1104
+ fi
1248
1105
  ;;
1249
- "󰮘 Remove files/folders (rm)")
1250
- # List tracked + untracked files for removal
1251
- files=$(git ls-files --cached --others --exclude-standard |
1252
- gum filter --no-limit --placeholder "Select files/folders to remove")
1253
-
1254
- if [[ -z "$files" ]]; then
1255
- print_warning "No files selected."
1256
- 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
1257
1110
  fi
1258
1111
 
1259
- echo "Files selected for removal:"
1260
- # Use 'tput' for better visual separation and color
1261
- tput setaf 3
1262
- echo "$files"
1263
- tput sgr0
1264
- echo
1265
-
1266
- if gum confirm "Confirm removal? This cannot be undone."; then
1267
- # Use a 'while read' loop to correctly handle filenames with spaces
1268
- echo "$files" | while read -r f; do
1269
- if [[ -z "$f" ]]; then continue; fi # Skip empty lines
1270
-
1271
- # Check if the file is tracked by git
1272
- if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
1273
- # It's a tracked file, use 'git rm'
1274
- git rm -rf "$f"
1275
- else
1276
- # It's an untracked file, use 'rm'
1277
- rm -rf "$f"
1278
- fi
1279
- done
1280
- 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
1281
1118
  else
1282
- print_status "Operation cancelled."
1119
+ print_status "Operation cancelled."
1283
1120
  fi
1284
1121
  ;;
1285
- " Uninitialize repo")
1286
- print_warning "This will completely uninitialize the Git repository in this folder."
1287
- echo "This action will delete the .git directory and cannot be undone!"
1288
- echo
1289
-
1290
- if gum confirm "Are you sure you want to continue?"; then
1291
- if [ -d ".git" ]; then
1292
- rm -rf .git
1293
- print_status "Git repository has been uninitialized."
1294
- else
1295
- print_error "No .git directory found here. Nothing to do."
1296
- fi
1297
- else
1298
- print_status "Operation cancelled."
1299
- fi
1122
+ "󰌍 Back")
1123
+ break
1300
1124
  ;;
1301
- "󰿅 Exit")
1302
- if gum confirm "Are you sure you want to exit?"; then
1303
- echo "Git Wizard vanishes in a puff of smoke..."
1304
- 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"
1305
1265
  else
1306
- continue
1266
+ remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
1307
1267
  fi
1308
- ;;
1309
- " Merge branches")
1310
- current_branch=$(get_current_branch)
1311
- print_status "You are on branch: $current_branch"
1312
- echo
1313
-
1314
- # Pick branch to merge from
1315
- merge_branch=$(git branch --format='%(refname:short)' |
1316
- grep -v "^$current_branch$" |
1317
- gum filter --placeholder "Select branch to merge into $current_branch")
1318
-
1319
- if [[ -z "$merge_branch" ]]; then
1320
- 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
1321
1275
  else
1322
- if gum confirm "Merge branch '$merge_branch' into '$current_branch'?"; then
1323
- echo "Merging branch '$merge_branch' into '$current_branch'..."
1324
- if gum spin --title "Merging..." -- git merge "$merge_branch"; then
1325
- print_status "Merge successful!"
1326
- else
1327
- print_error "Merge had conflicts! Resolve them manually."
1328
- fi
1329
- else
1330
- print_status "Merge cancelled."
1331
- fi
1276
+ print_warning "No remote selected."
1332
1277
  fi
1333
- ;;
1334
- " Contributor Statistics")
1335
- show_contributor_stats
1336
- ;;
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
1337
1290
 
1338
- "󰓗 Summon the Architect (.gitignore)")
1339
- summon_gitignore
1340
- ;;
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
1341
1334
 
1342
- "󰁯 Revert commit(s)")
1343
- print_status "Fetching commit history..."
1344
- 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
1345
1341
 
1346
- # Show commits as "<hash> <message>" but keep hash separately
1347
- commits=$(git log --oneline --decorate |
1348
- gum filter --no-limit --placeholder "Select commit(s) to revert" |
1349
- 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
1350
1346
 
1351
- if [[ -z "$commits" ]]; then
1352
- print_warning "No commit selected. Revert cancelled."
1353
- 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"
1354
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
1365
+
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
1355
1389
 
1356
- echo "You selected:"
1357
- echo "$commits"
1358
- echo
1359
-
1360
- if gum confirm "Confirm revert?"; then
1361
- for c in $commits; do
1362
- echo "Reverting commit $c..."
1363
- if git revert --no-edit "$c"; then
1364
- print_status "Commit $c reverted."
1365
- else
1366
- print_error "Conflict occurred while reverting $c!"
1367
- echo "Please resolve conflicts, then run:"
1368
- echo " git revert --continue"
1369
- break
1370
- fi
1371
- done
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!"
1372
1402
  else
1373
- print_status "Revert cancelled."
1403
+ print_error "Merge had conflicts! Resolve them manually."
1374
1404
  fi
1375
- ;;
1405
+ else
1406
+ print_status "Merge cancelled."
1407
+ fi
1408
+ fi
1409
+ ;;
1410
+ " Contributor Statistics")
1411
+ show_contributor_stats
1412
+ ;;
1376
1413
 
1377
- " Git graph")
1378
- color=${YELLOW}
1379
- line1="Git graph"
1380
- line2="[INFO] press 'q' to exit"
1381
-
1382
- echo -e "${color}╭$(printf '─%.0s' $(seq 1 30))╮${NC}"
1383
- printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line1"
1384
- printf "${color}│${NC} %-*s ${color}│${NC}\n" 28 "$line2"
1385
- echo -e "${color}╰$(printf '─%.0s' $(seq 1 30))╯${NC}"
1386
-
1387
- # Use gum spin while loading the graph.
1388
- # Since git log can be long, we pipe it to less if it's too big,
1389
- # but gum spin doesn't work well with interactive pagers.
1390
- # However, for a simple "pretty log", we can just run it.
1391
- gum spin --title "Drawing git graph..." -- sleep 2
1392
- pretty_git_log
1393
- ;;
1414
+ "󰓗 Summon the Architect (.gitignore)")
1415
+ summon_gitignore
1416
+ ;;
1394
1417
 
1395
- *)
1396
- print_error "Invalid choice! Try again."
1397
- echo "Git Wizard vanishes in a puff of smoke..."
1398
- break
1399
- ;;
1400
- esac
1418
+ "󰔪 Summon the Resurrection Stone (Recover lost code)")
1419
+ resurrect_commit
1420
+ ;;
1401
1421
 
1422
+ "󰁯 Revert commit(s)")
1423
+ print_status "Fetching commit history..."
1402
1424
  echo
1403
- gum style --foreground "$BIMAGIC_MUTED" "Press Enter to continue..."
1404
- read -r </dev/tty
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"
1438
+ echo
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
1405
1485
  done