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.
- package/README.md +29 -5
- package/bimagic +1209 -1129
- package/package.json +2 -4
package/bimagic
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
VERSION="v1.
|
|
3
|
+
VERSION="v1.5.0"
|
|
4
4
|
|
|
5
5
|
if [[ "$1" == "--version" || "$1" == "-v" ]]; then
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
-
|
|
122
|
+
gum style --foreground "$BIMAGIC_PRIMARY" "$1"
|
|
123
|
+
play_sound "success"
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
print_error() {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
gum style --foreground "$BIMAGIC_ERROR" "$1"
|
|
128
|
+
play_sound "error"
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
print_warning() {
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
+
local remote_name=${1:-"origin"}
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
165
|
+
# Ask for protocol
|
|
166
|
+
local protocol=$(gum choose --header "Select protocol for '$remote_name':" "HTTPS (Token)" "SSH")
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
224
|
+
status="🟢 clean"
|
|
225
|
+
color="$BIMAGIC_SUCCESS"
|
|
215
226
|
fi
|
|
227
|
+
else
|
|
228
|
+
status="🟡 uncommitted"
|
|
229
|
+
color="$BIMAGIC_WARNING"
|
|
230
|
+
fi
|
|
216
231
|
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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
|
-
|
|
368
|
+
rm -f "$temp_file"
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
if [[ -z "$stats" ]]; then
|
|
371
|
+
print_error "No contribution data found for the selected period."
|
|
372
|
+
return 1
|
|
373
|
+
fi
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
# Display header
|
|
382
|
+
echo -e "${PURPLE}Contribution Report ($time_range)${NC}"
|
|
383
|
+
echo "$(printf '─%.0s' $(seq 1 45))"
|
|
384
384
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
391
|
-
|
|
390
|
+
# Generate themed bar
|
|
391
|
+
local bar=$(generate_bar "$percentage")
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
412
|
+
done <<<"$stats"
|
|
413
413
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
424
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
464
|
+
if [[ $status -ne 0 || ! -d "$repo_name" ]]; then
|
|
465
|
+
print_error "Clone failed."
|
|
466
|
+
return 1
|
|
444
467
|
fi
|
|
445
468
|
|
|
446
|
-
|
|
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
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
513
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
746
|
-
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
760
|
-
|
|
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
|
-
|
|
791
|
+
print_status " Spinning the Time Turner..."
|
|
766
792
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
if
|
|
776
|
-
|
|
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
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
print_status "
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
864
|
-
|
|
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
|
-
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
-
|
|
902
|
-
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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
|
|
1063
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1101
|
+
print_error "Failed to drop $stash_id"
|
|
1176
1102
|
fi
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
1119
|
+
print_status "Operation cancelled."
|
|
1283
1120
|
fi
|
|
1284
1121
|
;;
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
1266
|
+
remote=$(echo "$remotes" | gum filter --placeholder "Select remote to pull from")
|
|
1307
1267
|
fi
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1335
|
-
|
|
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
|
-
|
|
1339
|
-
|
|
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
|
-
"
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
1352
|
-
|
|
1353
|
-
|
|
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
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
1404
|
-
|
|
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
|