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