gitbash 1.9.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -89
- package/bin/gitbash +345 -723
- package/commands/_utils.sh +485 -157
- package/commands/branch.sh +9 -12
- package/commands/cleanstash.sh +47 -122
- package/commands/cleanup.sh +305 -441
- package/commands/commit.sh +136 -172
- package/commands/commits.sh +81 -114
- package/commands/create.sh +231 -314
- package/commands/pr.sh +158 -146
- package/commands/stale.sh +137 -277
- package/commands/stash.sh +11 -39
- package/commands/stashes.sh +10 -13
- package/commands/status.sh +197 -250
- package/commands/switch.sh +186 -274
- package/commands/unstash.sh +45 -109
- package/commands/update.sh +123 -142
- package/docs/CONTRIBUTING.md +16 -5
- package/docs/SECURITY.md +14 -3
- package/package.json +5 -3
package/commands/stale.sh
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# shellcheck disable=SC2155
|
|
3
2
|
|
|
4
3
|
# Source common utilities
|
|
5
|
-
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]
|
|
4
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
5
|
# shellcheck source=_utils.sh
|
|
7
6
|
source "$SOURCE_DIR/_utils.sh"
|
|
8
7
|
|
|
8
|
+
# Unix timestamp of N months ago
|
|
9
|
+
_stale_months_ago() {
|
|
10
|
+
local months="$1" ts
|
|
11
|
+
ts=$(date -v-"${months}m" +%s 2>/dev/null) ||
|
|
12
|
+
ts=$(date -d "${months} months ago" +%s 2>/dev/null) ||
|
|
13
|
+
ts=$(( $(date +%s) - months * 30 * 86400 ))
|
|
14
|
+
echo "$ts"
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
stale() {
|
|
10
|
-
# -----------------------------
|
|
11
|
-
# 0. Check for help/version flag and parse options
|
|
12
|
-
# -----------------------------
|
|
13
18
|
local json_mode=false
|
|
14
19
|
local my_mode=false
|
|
15
20
|
local all_mode=false
|
|
16
21
|
local filter_args=()
|
|
17
22
|
local stale_months="${GITBASH_STALE_MONTHS:-3}"
|
|
18
|
-
|
|
23
|
+
|
|
19
24
|
while [[ $# -gt 0 ]]; do
|
|
20
25
|
case "$1" in
|
|
21
26
|
-v|--version)
|
|
@@ -26,8 +31,8 @@ stale() {
|
|
|
26
31
|
cat << 'EOF'
|
|
27
32
|
Usage: stale [OPTIONS] [FILTER...]
|
|
28
33
|
|
|
29
|
-
Show
|
|
30
|
-
|
|
34
|
+
Show remote branches ordered by last commit date (oldest first) and delete
|
|
35
|
+
the ones that are no longer needed.
|
|
31
36
|
|
|
32
37
|
Arguments:
|
|
33
38
|
FILTER... Optional search filter words to pre-fill fzf (joined with spaces)
|
|
@@ -39,57 +44,30 @@ Options:
|
|
|
39
44
|
--json Output branch data as JSON (non-interactive)
|
|
40
45
|
--age=N Override stale threshold in months (default: 3, configurable via GITBASH_STALE_MONTHS)
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- By default, only shows branches older than N months (default 3)
|
|
45
|
-
- Shows branch name, relative date, and author
|
|
46
|
-
- Interactive selection with fzf
|
|
47
|
-
- Preview shows recent commits on the selected branch
|
|
48
|
-
- Delete selected branches directly from the list
|
|
49
|
-
|
|
50
|
-
Configuration:
|
|
51
|
-
GITBASH_STALE_MONTHS - Set default threshold in months (run 'gitbash --config')
|
|
47
|
+
Protected branches (the base branch and GITBASH_PROTECTED_BRANCHES, default
|
|
48
|
+
"main master develop release/*") are never listed and cannot be deleted.
|
|
52
49
|
|
|
53
50
|
Navigation:
|
|
54
|
-
↑/↓
|
|
51
|
+
↑/↓ Navigate through branches
|
|
55
52
|
TAB Select/deselect branch (multi-select)
|
|
56
|
-
Ctrl-
|
|
57
|
-
Enter Delete selected branch(es)
|
|
53
|
+
Ctrl-T Toggle between stale and all branches
|
|
54
|
+
Enter Delete selected branch(es) from the remote (asks first)
|
|
58
55
|
ESC/Ctrl-C Exit without action
|
|
59
56
|
|
|
60
57
|
Examples:
|
|
61
58
|
$ stale
|
|
62
|
-
Stale branches (oldest first) >
|
|
63
|
-
> feature/very-old 6 months ago Bob Wilson
|
|
64
|
-
feature/another-old-one 4 months ago Jane Smith
|
|
65
|
-
feature/old-feature 3 months ago John Doe
|
|
66
|
-
✖ Abort
|
|
67
|
-
|
|
68
|
-
$ stale --age=1
|
|
69
|
-
# Show branches older than 1 month
|
|
70
|
-
|
|
71
59
|
$ stale --age=6
|
|
72
|
-
# Show branches older than 6 months
|
|
73
|
-
|
|
74
60
|
$ stale --all
|
|
75
|
-
# Start with all branches visible (including recent ones)
|
|
76
|
-
# Press Ctrl-A to toggle between all/stale view
|
|
77
|
-
|
|
78
61
|
$ stale --my
|
|
79
|
-
# Pre-fills fzf filter with your git username to show only your branches
|
|
80
|
-
|
|
81
62
|
$ stale Product refactoring
|
|
82
|
-
# Pre-fills fzf filter with "Product refactoring"
|
|
83
|
-
|
|
84
63
|
$ stale --json
|
|
85
|
-
# Output JSON for scripting
|
|
86
64
|
|
|
87
65
|
Output format:
|
|
88
|
-
<branch-name>
|
|
66
|
+
<branch-name> <relative-date> <last author>
|
|
89
67
|
|
|
90
68
|
Requirements:
|
|
91
69
|
- Must be in a git repository
|
|
92
|
-
- fzf (
|
|
70
|
+
- fzf (not required for --json)
|
|
93
71
|
|
|
94
72
|
EOF
|
|
95
73
|
return 0
|
|
@@ -108,12 +86,16 @@ EOF
|
|
|
108
86
|
;;
|
|
109
87
|
--age=*)
|
|
110
88
|
stale_months="${1#--age=}"
|
|
111
|
-
if ! [[ "$stale_months" =~ ^[
|
|
89
|
+
if ! [[ "$stale_months" =~ ^[1-9][0-9]*$ ]]; then
|
|
112
90
|
print_error "Invalid age value: $stale_months"
|
|
113
91
|
return 1
|
|
114
92
|
fi
|
|
115
93
|
shift
|
|
116
94
|
;;
|
|
95
|
+
-*)
|
|
96
|
+
print_error "Unknown option: $1"
|
|
97
|
+
return 1
|
|
98
|
+
;;
|
|
117
99
|
*)
|
|
118
100
|
filter_args+=("$1")
|
|
119
101
|
shift
|
|
@@ -122,221 +104,116 @@ EOF
|
|
|
122
104
|
done
|
|
123
105
|
|
|
124
106
|
# -----------------------------
|
|
125
|
-
# 1.
|
|
107
|
+
# 1. Prerequisites
|
|
126
108
|
# -----------------------------
|
|
127
109
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
128
|
-
if [[ "$json_mode" == true ]]; then
|
|
129
|
-
echo "[]"
|
|
130
|
-
else
|
|
131
|
-
echo "Not inside a git repository."
|
|
132
|
-
fi
|
|
110
|
+
if [[ "$json_mode" == true ]]; then echo "[]"; else print_error "Not inside a git repository."; fi
|
|
133
111
|
return 1
|
|
134
112
|
fi
|
|
135
|
-
|
|
136
113
|
if [[ "$json_mode" == false ]]; then
|
|
137
114
|
require_fzf || return 1
|
|
138
115
|
fi
|
|
139
116
|
|
|
140
|
-
# -----------------------------
|
|
141
|
-
# 2. Handle filter (--my flag or arbitrary words)
|
|
142
|
-
# -----------------------------
|
|
143
117
|
local filter=""
|
|
144
118
|
if [[ "$my_mode" == true ]]; then
|
|
145
119
|
filter=$(git config user.name)
|
|
146
120
|
if [[ -z "$filter" && "$json_mode" == false ]]; then
|
|
147
|
-
|
|
121
|
+
print_warning "git config user.name is not set."
|
|
148
122
|
fi
|
|
149
123
|
elif [[ ${#filter_args[@]} -gt 0 ]]; then
|
|
150
124
|
filter="${filter_args[*]}"
|
|
151
125
|
fi
|
|
152
126
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
fi
|
|
159
|
-
if ! git fetch --prune origin 2>/dev/null; then
|
|
160
|
-
if [[ "$json_mode" == false ]]; then
|
|
161
|
-
echo "⚠ Fetch failed; continuing with local data." >&2
|
|
162
|
-
fi
|
|
127
|
+
local remote
|
|
128
|
+
remote=$(gb_remote)
|
|
129
|
+
[[ "$json_mode" == false ]] && echo "Fetching latest from '$remote'..."
|
|
130
|
+
if ! git fetch --prune --quiet "$remote" 2>/dev/null; then
|
|
131
|
+
[[ "$json_mode" == false ]] && print_warning "Fetch failed; continuing with local data."
|
|
163
132
|
fi
|
|
133
|
+
GB_BASE=$(gb_base_branch 2>/dev/null) || GB_BASE=""
|
|
164
134
|
|
|
165
135
|
# -----------------------------
|
|
166
|
-
#
|
|
136
|
+
# 2. Build branch lists (oldest first)
|
|
167
137
|
# -----------------------------
|
|
168
|
-
local abort_label="✖ Abort"
|
|
169
|
-
local max_branch_length=65
|
|
170
|
-
|
|
171
|
-
# Calculate date N months ago (based on stale_months)
|
|
172
138
|
local threshold_ago
|
|
173
|
-
threshold_ago=$(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
local
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# Remove origin/ prefix for display
|
|
183
|
-
local full_branch="${branch#origin/}"
|
|
184
|
-
local display_branch="$full_branch"
|
|
185
|
-
# Truncate branch name if too long
|
|
186
|
-
if [[ ${#display_branch} -gt $max_branch_length ]]; then
|
|
187
|
-
display_branch="${display_branch:0:$((max_branch_length - 3))}..."
|
|
188
|
-
fi
|
|
189
|
-
# Format: display branch (padded), date, author, TAB, full branch name
|
|
190
|
-
printf "%-${max_branch_length}s %-20s %-25s\t%s\n" "$display_branch" "$date" "$author" "$full_branch"
|
|
191
|
-
done || true)
|
|
192
|
-
|
|
193
|
-
# Build stale branch list (only branches older than 3 months)
|
|
194
|
-
local stale_branch_list
|
|
195
|
-
stale_branch_list=$(git for-each-ref --sort=committerdate --format='%(refname:short)|%(committerdate:relative)|%(authorname)|%(committerdate:unix)' refs/remotes/origin | \
|
|
196
|
-
grep -v 'origin/HEAD' | \
|
|
197
|
-
grep -v '^origin|' | \
|
|
198
|
-
while IFS='|' read -r branch date author timestamp; do
|
|
199
|
-
# Only include if older than 3 months
|
|
200
|
-
if [[ "$timestamp" -lt "$threshold_ago" ]]; then
|
|
201
|
-
# Remove origin/ prefix for display
|
|
202
|
-
local full_branch="${branch#origin/}"
|
|
203
|
-
local display_branch="$full_branch"
|
|
204
|
-
# Truncate branch name if too long
|
|
205
|
-
if [[ ${#display_branch} -gt $max_branch_length ]]; then
|
|
206
|
-
display_branch="${display_branch:0:$((max_branch_length - 3))}..."
|
|
207
|
-
fi
|
|
208
|
-
# Format: display branch (padded), date, author, TAB, full branch name
|
|
209
|
-
printf "%-${max_branch_length}s %-20s %-25s\t%s\n" "$display_branch" "$date" "$author" "$full_branch"
|
|
210
|
-
fi
|
|
211
|
-
done || true)
|
|
139
|
+
threshold_ago=$(_stale_months_ago "$stale_months")
|
|
140
|
+
|
|
141
|
+
local max_len=65
|
|
142
|
+
local all_list="" stale_list="" json="[" first=true
|
|
143
|
+
local sep=$'\x1f' ref ts rel email name branch display row
|
|
144
|
+
while IFS="$sep" read -r ref ts rel email name; do
|
|
145
|
+
[[ -z "$ref" || "$ref" == "$remote" || "$ref" == "$remote/HEAD" ]] && continue
|
|
146
|
+
branch="${ref#"$remote"/}"
|
|
147
|
+
gb_is_protected "$branch" && continue
|
|
212
148
|
|
|
213
|
-
if [[ -z "$all_branch_list" ]]; then
|
|
214
149
|
if [[ "$json_mode" == true ]]; then
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
150
|
+
if [[ "$all_mode" == true || "$ts" -lt "$threshold_ago" ]]; then
|
|
151
|
+
email="${email#<}"
|
|
152
|
+
email="${email%>}"
|
|
153
|
+
[[ "$first" == true ]] && first=false || json+=","
|
|
154
|
+
json+="{\"last_change_timestamp\":$ts,\"author_email\":\"$(gb_json_escape "$email")\",\"author_name\":\"$(gb_json_escape "$name")\",\"name\":\"$(gb_json_escape "$branch")\",\"last_change_relative\":\"$(gb_json_escape "$rel")\"}"
|
|
155
|
+
fi
|
|
156
|
+
continue
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
display="$branch"
|
|
160
|
+
if [[ ${#display} -gt $max_len ]]; then
|
|
161
|
+
display="${display:0:$((max_len - 3))}..."
|
|
162
|
+
fi
|
|
163
|
+
row=$(printf "%-${max_len}s %-20s %s" "$display" "$rel" "$name")
|
|
164
|
+
row+=$'\t'"$branch"$'\n'
|
|
165
|
+
all_list+="$row"
|
|
166
|
+
if [[ "$ts" -lt "$threshold_ago" ]]; then
|
|
167
|
+
stale_list+="$row"
|
|
218
168
|
fi
|
|
169
|
+
done < <(git for-each-ref --sort=committerdate \
|
|
170
|
+
--format="%(refname:short)%1f%(committerdate:unix)%1f%(committerdate:relative)%1f%(authoremail)%1f%(authorname)" \
|
|
171
|
+
"refs/remotes/$remote" 2>/dev/null)
|
|
172
|
+
|
|
173
|
+
if [[ "$json_mode" == true ]]; then
|
|
174
|
+
echo "$json]"
|
|
219
175
|
return 0
|
|
220
176
|
fi
|
|
221
177
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# -----------------------------
|
|
225
|
-
if [[ "$json_mode" == true ]]; then
|
|
226
|
-
local json_output="["
|
|
227
|
-
local first=true
|
|
228
|
-
|
|
229
|
-
# Helper function to escape JSON strings
|
|
230
|
-
_json_escape() {
|
|
231
|
-
local str="$1"
|
|
232
|
-
str="${str//\\/\\\\}"
|
|
233
|
-
str="${str//\"/\\\"}"
|
|
234
|
-
str="${str//$'\n'/\\n}"
|
|
235
|
-
str="${str//$'\r'/\\r}"
|
|
236
|
-
str="${str//$'\t'/\\t}"
|
|
237
|
-
echo "$str"
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
# Build JSON from branches (all or stale based on --all flag)
|
|
241
|
-
{
|
|
242
|
-
while IFS='|' read -r branch rel_date author_name author_email timestamp; do
|
|
243
|
-
[[ -z "$branch" ]] && continue
|
|
244
|
-
# Include based on all_mode flag
|
|
245
|
-
if [[ "$all_mode" == true ]] || [[ "$timestamp" -lt "$threshold_ago" ]]; then
|
|
246
|
-
local name="${branch#origin/}"
|
|
247
|
-
# Remove angle brackets from email
|
|
248
|
-
author_email="${author_email#<}"
|
|
249
|
-
author_email="${author_email%>}"
|
|
250
|
-
|
|
251
|
-
# Escape values for JSON
|
|
252
|
-
name=$(_json_escape "$name")
|
|
253
|
-
rel_date=$(_json_escape "$rel_date")
|
|
254
|
-
author_email=$(_json_escape "$author_email")
|
|
255
|
-
author_name=$(_json_escape "$author_name")
|
|
256
|
-
|
|
257
|
-
if [[ "$first" == true ]]; then
|
|
258
|
-
first=false
|
|
259
|
-
else
|
|
260
|
-
json_output+=","
|
|
261
|
-
fi
|
|
262
|
-
|
|
263
|
-
json_output+="{\"last_change_timestamp\":$timestamp,\"author_email\":\"$author_email\",\"author_name\":\"$author_name\",\"name\":\"$name\",\"last_change_relative\":\"$rel_date\"}"
|
|
264
|
-
fi
|
|
265
|
-
done < <(git for-each-ref --sort=committerdate --format='%(refname:short)|%(committerdate:relative)|%(authorname)|%(authoremail)|%(committerdate:unix)' refs/remotes/origin 2>/dev/null | grep -v 'origin/HEAD' | grep -v '^origin|' || true)
|
|
266
|
-
} &>/dev/null
|
|
267
|
-
json_output+="]"
|
|
268
|
-
echo "$json_output"
|
|
178
|
+
if [[ -z "$all_list" ]]; then
|
|
179
|
+
echo "No deletable remote branches found."
|
|
269
180
|
return 0
|
|
270
181
|
fi
|
|
271
|
-
|
|
272
|
-
#
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
local
|
|
276
|
-
local
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
trap 'rm -f "$stale_file" "$all_file" "$state_file" "$toggle_script"' EXIT INT TERM
|
|
280
|
-
|
|
281
|
-
# Write stale branches with header
|
|
182
|
+
|
|
183
|
+
# -----------------------------
|
|
184
|
+
# 3. fzf with a stale/all toggle (Ctrl-T)
|
|
185
|
+
# -----------------------------
|
|
186
|
+
local abort_label="✖ Abort"
|
|
187
|
+
local keys="[TAB] select | [Ctrl-T] toggle all/stale | [Enter] delete | [ESC] exit"
|
|
188
|
+
local tmp_dir
|
|
189
|
+
tmp_dir=$(mktemp -d)
|
|
282
190
|
{
|
|
283
|
-
echo "══ Stale branches (>${stale_months}mo, oldest first) ══
|
|
284
|
-
|
|
285
|
-
echo "$stale_branch_list"
|
|
286
|
-
fi
|
|
191
|
+
echo "══ Stale branches (>${stale_months}mo, oldest first) ══ $keys"
|
|
192
|
+
printf '%s' "$stale_list"
|
|
287
193
|
echo "$abort_label"
|
|
288
|
-
} > "$
|
|
289
|
-
|
|
290
|
-
# Write all branches with header
|
|
194
|
+
} > "$tmp_dir/stale"
|
|
291
195
|
{
|
|
292
|
-
echo "══ All branches (oldest first) ══
|
|
293
|
-
|
|
294
|
-
echo "$all_branch_list"
|
|
295
|
-
fi
|
|
196
|
+
echo "══ All branches (oldest first) ══ $keys"
|
|
197
|
+
printf '%s' "$all_list"
|
|
296
198
|
echo "$abort_label"
|
|
297
|
-
} > "$
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
printf "stale" > "$state_file"
|
|
304
|
-
fi
|
|
305
|
-
|
|
306
|
-
# -----------------------------
|
|
307
|
-
# 6. Run fzf picker with preview
|
|
308
|
-
# -----------------------------
|
|
309
|
-
local stale_count=$(echo "$stale_branch_list" | grep -c . || echo "0")
|
|
310
|
-
local all_count=$(echo "$all_branch_list" | grep -c . || echo "0")
|
|
311
|
-
|
|
312
|
-
# Create toggle script
|
|
313
|
-
cat > "$toggle_script" << TOGGLE_EOF
|
|
314
|
-
#!/bin/bash
|
|
315
|
-
state=\$(cat "$state_file" | tr -d '\n')
|
|
316
|
-
# Toggle: if currently stale, switch to all; if currently all, switch to stale
|
|
317
|
-
if [[ "\$state" == "stale" ]]; then
|
|
318
|
-
# Switching FROM stale TO all
|
|
319
|
-
printf "all" > "$state_file"
|
|
320
|
-
cat "$all_file"
|
|
199
|
+
} > "$tmp_dir/all"
|
|
200
|
+
if [[ "$all_mode" == true ]]; then echo "all" > "$tmp_dir/state"; else echo "stale" > "$tmp_dir/state"; fi
|
|
201
|
+
cat > "$tmp_dir/toggle" << 'TOGGLE_EOF'
|
|
202
|
+
dir="$1"
|
|
203
|
+
if [[ "$(cat "$dir/state")" == "stale" ]]; then
|
|
204
|
+
echo "all" > "$dir/state"
|
|
321
205
|
else
|
|
322
|
-
|
|
323
|
-
printf "stale" > "$state_file"
|
|
324
|
-
cat "$stale_file"
|
|
206
|
+
echo "stale" > "$dir/state"
|
|
325
207
|
fi
|
|
208
|
+
cat "$dir/$(cat "$dir/state")"
|
|
326
209
|
TOGGLE_EOF
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
initial_input_file="$all_file"
|
|
333
|
-
else
|
|
334
|
-
initial_input_file="$stale_file"
|
|
335
|
-
fi
|
|
336
|
-
|
|
210
|
+
|
|
211
|
+
local tmp_q remote_q
|
|
212
|
+
printf -v tmp_q '%q' "$tmp_dir"
|
|
213
|
+
printf -v remote_q '%q' "$remote"
|
|
214
|
+
|
|
337
215
|
local selection
|
|
338
|
-
selection=$(
|
|
339
|
-
cat "$initial_input_file" | fzf \
|
|
216
|
+
selection=$(run_fzf \
|
|
340
217
|
--query="$filter" \
|
|
341
218
|
-i \
|
|
342
219
|
--reverse \
|
|
@@ -345,81 +222,64 @@ TOGGLE_EOF
|
|
|
345
222
|
--multi \
|
|
346
223
|
--delimiter=$'\t' \
|
|
347
224
|
--with-nth=1 \
|
|
348
|
-
--bind=
|
|
349
|
-
--
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
echo "Branch: origin/$branch";
|
|
358
|
-
echo "";
|
|
359
|
-
echo "Recent commits:";
|
|
360
|
-
git log --oneline --color=always -n 15 "origin/$branch" 2>/dev/null || echo "No commits found";
|
|
361
|
-
fi
|
|
362
|
-
' \
|
|
225
|
+
--bind="ctrl-t:reload(bash $tmp_q/toggle $tmp_q)+clear-query" \
|
|
226
|
+
--preview="
|
|
227
|
+
branch=\$(printf '%s' {} | cut -f2)
|
|
228
|
+
if [[ -z \"\$branch\" ]]; then echo 'Exit without action'; exit 0; fi
|
|
229
|
+
echo \"Branch: $remote_q/\$branch\"
|
|
230
|
+
echo
|
|
231
|
+
echo 'Recent commits:'
|
|
232
|
+
git log --oneline --color=always -n 15 \"refs/remotes/$remote_q/\$branch\" 2>/dev/null || echo 'No commits found'
|
|
233
|
+
" \
|
|
363
234
|
--preview-window=right:35% \
|
|
364
|
-
< "$
|
|
365
|
-
)
|
|
235
|
+
< "$tmp_dir/$(cat "$tmp_dir/state")"
|
|
236
|
+
) || true
|
|
237
|
+
rm -rf "$tmp_dir"
|
|
366
238
|
|
|
367
|
-
# ESC or Ctrl-C
|
|
368
239
|
if [[ -z "$selection" ]]; then
|
|
369
240
|
echo "Exited."
|
|
370
241
|
return 0
|
|
371
242
|
fi
|
|
372
243
|
|
|
373
|
-
# Abort option
|
|
374
|
-
if echo "$selection" | grep -q "^$abort_label"; then
|
|
375
|
-
echo "Aborted."
|
|
376
|
-
return 0
|
|
377
|
-
fi
|
|
378
|
-
|
|
379
244
|
# -----------------------------
|
|
380
|
-
#
|
|
245
|
+
# 4. Confirm and delete
|
|
381
246
|
# -----------------------------
|
|
382
|
-
local branches_to_delete=()
|
|
247
|
+
local branches_to_delete=() line
|
|
383
248
|
while IFS= read -r line; do
|
|
384
|
-
|
|
385
|
-
|
|
249
|
+
branch=$(printf '%s' "$line" | cut -f2)
|
|
250
|
+
[[ -z "$branch" ]] && continue
|
|
251
|
+
# Never delete protected branches, even if they got into the list somehow
|
|
252
|
+
if gb_is_protected "$branch"; then
|
|
253
|
+
print_warning "Skipping protected branch '$branch'."
|
|
386
254
|
continue
|
|
387
255
|
fi
|
|
388
|
-
|
|
389
|
-
local branch_name
|
|
390
|
-
branch_name=$(echo "$line" | cut -f2)
|
|
391
|
-
if [[ -n "$branch_name" ]]; then
|
|
392
|
-
branches_to_delete+=("$branch_name")
|
|
393
|
-
fi
|
|
256
|
+
branches_to_delete+=("$branch")
|
|
394
257
|
done <<< "$selection"
|
|
395
258
|
|
|
396
259
|
if [[ ${#branches_to_delete[@]} -eq 0 ]]; then
|
|
397
|
-
echo "
|
|
260
|
+
echo "Aborted."
|
|
398
261
|
return 0
|
|
399
262
|
fi
|
|
400
263
|
|
|
401
|
-
echo
|
|
402
|
-
echo "Selected branches to delete from remote:"
|
|
264
|
+
echo
|
|
265
|
+
echo "Selected branches to delete from '$remote':"
|
|
266
|
+
printf ' - %s\n' "${branches_to_delete[@]}"
|
|
267
|
+
echo
|
|
268
|
+
|
|
269
|
+
if ! gb_confirm --strict "Delete these ${#branches_to_delete[@]} branch(es) from '$remote'? This cannot be undone from here." n; then
|
|
270
|
+
echo "Deletion cancelled."
|
|
271
|
+
return 0
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
local failed=0 out
|
|
403
275
|
for branch in "${branches_to_delete[@]}"; do
|
|
404
|
-
|
|
276
|
+
if out=$(git push "$remote" --delete "$branch" 2>&1); then
|
|
277
|
+
print_success "Deleted $remote/$branch"
|
|
278
|
+
else
|
|
279
|
+
print_error "Failed to delete $remote/$branch:"
|
|
280
|
+
printf '%s\n' "$out" | sed 's/^/ /' >&2
|
|
281
|
+
failed=$((failed + 1))
|
|
282
|
+
fi
|
|
405
283
|
done
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
prompt_read "Delete these ${#branches_to_delete[@]} branch(es) from remote? (y/N): " confirm
|
|
409
|
-
case "$confirm" in
|
|
410
|
-
[yY][eE][sS]|[yY])
|
|
411
|
-
echo "Deleting branches..."
|
|
412
|
-
for branch in "${branches_to_delete[@]}"; do
|
|
413
|
-
echo "Deleting origin/$branch ..."
|
|
414
|
-
if git push origin --delete "$branch" 2>/dev/null; then
|
|
415
|
-
echo "✓ Deleted origin/$branch"
|
|
416
|
-
else
|
|
417
|
-
echo "✗ Failed to delete origin/$branch"
|
|
418
|
-
fi
|
|
419
|
-
done
|
|
420
|
-
;;
|
|
421
|
-
*)
|
|
422
|
-
echo "Deletion cancelled."
|
|
423
|
-
;;
|
|
424
|
-
esac
|
|
284
|
+
[[ $failed -eq 0 ]]
|
|
425
285
|
}
|
package/commands/stash.sh
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# shellcheck disable=SC2155
|
|
3
2
|
|
|
4
3
|
# Source common utilities
|
|
5
|
-
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]
|
|
4
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
5
|
# shellcheck source=_utils.sh
|
|
7
6
|
source "$SOURCE_DIR/_utils.sh"
|
|
8
7
|
|
|
9
8
|
stash() {
|
|
10
|
-
# -----------------------------
|
|
11
|
-
# 0. Check for help/version flag
|
|
12
|
-
# -----------------------------
|
|
13
9
|
if [[ "${1:-}" == "-v" || "${1:-}" == "--version" ]]; then
|
|
14
10
|
echo "gitbash ${FUNCNAME[0]} v$VERSION"
|
|
15
11
|
return 0
|
|
@@ -25,29 +21,20 @@ Options:
|
|
|
25
21
|
-h, --help Show this help message
|
|
26
22
|
|
|
27
23
|
Features:
|
|
28
|
-
- Stashes all
|
|
24
|
+
- Stashes all changes: staged, unstaged and untracked files
|
|
29
25
|
- Custom stash message for easy identification
|
|
30
|
-
- Interactive mode if no name provided
|
|
31
|
-
- Works with untracked files
|
|
32
26
|
|
|
33
27
|
Examples:
|
|
34
|
-
# Interactive mode (prompts for name)
|
|
35
28
|
$ stash
|
|
36
29
|
Stash name: work in progress on login
|
|
37
30
|
✓ Created stash: work in progress on login
|
|
38
31
|
|
|
39
|
-
# Direct mode (name as arguments)
|
|
40
32
|
$ stash fix for authentication bug
|
|
41
33
|
✓ Created stash: fix for authentication bug
|
|
42
34
|
|
|
43
|
-
# View your stashes
|
|
44
|
-
$ git stash list
|
|
45
|
-
stash@{0}: On main: fix for authentication bug
|
|
46
|
-
stash@{1}: On main: work in progress on login
|
|
47
|
-
|
|
48
35
|
Requirements:
|
|
49
|
-
- Must be in a git repository
|
|
50
|
-
- Must have changes to stash
|
|
36
|
+
- Must be in a git repository with at least one commit
|
|
37
|
+
- Must have changes to stash
|
|
51
38
|
|
|
52
39
|
See also:
|
|
53
40
|
stashes -h Show help for stashes menu
|
|
@@ -58,45 +45,30 @@ EOF
|
|
|
58
45
|
return 0
|
|
59
46
|
fi
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
echo "Error: Not inside a git repository."
|
|
48
|
+
require_git_repo || return 1
|
|
49
|
+
|
|
50
|
+
if ! git rev-parse --verify --quiet HEAD >/dev/null; then
|
|
51
|
+
print_error "Cannot stash before the first commit."
|
|
66
52
|
return 1
|
|
67
53
|
fi
|
|
68
54
|
|
|
69
|
-
|
|
70
|
-
# 2. Check for changes to stash
|
|
71
|
-
# -----------------------------
|
|
72
|
-
if git diff --quiet && git diff --cached --quiet && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
|
|
55
|
+
if [[ -z "$(git status --porcelain)" ]]; then
|
|
73
56
|
echo "No changes to stash."
|
|
74
57
|
return 1
|
|
75
58
|
fi
|
|
76
59
|
|
|
77
|
-
# -----------------------------
|
|
78
|
-
# 3. Get stash name
|
|
79
|
-
# -----------------------------
|
|
80
60
|
local stash_name
|
|
81
|
-
|
|
82
61
|
if [[ $# -gt 0 ]]; then
|
|
83
|
-
# Name provided as arguments
|
|
84
62
|
stash_name="$*"
|
|
85
63
|
else
|
|
86
|
-
|
|
87
|
-
echo -n "Stash name: "
|
|
88
|
-
read -r stash_name
|
|
89
|
-
|
|
64
|
+
prompt_read "Stash name: " stash_name || true
|
|
90
65
|
if [[ -z "$stash_name" ]]; then
|
|
91
66
|
print_error "Stash name cannot be empty."
|
|
92
67
|
return 1
|
|
93
68
|
fi
|
|
94
69
|
fi
|
|
95
70
|
|
|
96
|
-
|
|
97
|
-
# 4. Create the stash
|
|
98
|
-
# -----------------------------
|
|
99
|
-
if git stash push -u -m "$stash_name"; then
|
|
71
|
+
if git stash push --include-untracked -m "$stash_name"; then
|
|
100
72
|
print_success "Created stash: $stash_name"
|
|
101
73
|
else
|
|
102
74
|
print_error "Failed to create stash."
|