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/switch.sh
CHANGED
|
@@ -1,344 +1,256 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
## switch selects and switches git branches using fzf
|
|
3
|
-
# shellcheck shell=bash
|
|
4
|
-
# shellcheck disable=SC2155
|
|
5
2
|
|
|
6
3
|
# Source common utilities
|
|
7
|
-
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]
|
|
4
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
5
|
# shellcheck source=_utils.sh
|
|
9
6
|
source "$SOURCE_DIR/_utils.sh"
|
|
10
7
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
# Print the branch list for fzf: "<label>: <name>\t<type>\t<name>"
|
|
9
|
+
_switch_list_branches() {
|
|
10
|
+
local current base="" base_ref merged="" name remote_name local_names
|
|
11
|
+
current=$(gb_current_branch) || current=""
|
|
12
|
+
if base=$(gb_base_branch 2>/dev/null); then
|
|
13
|
+
base_ref=$(gb_base_ref "$base")
|
|
14
|
+
merged=$(git for-each-ref --merged "$base_ref" --format='%(refname:short)' refs/heads 2>/dev/null)
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
local_names=$(git for-each-ref --format='%(refname:short)' refs/heads)
|
|
18
|
+
while IFS= read -r name; do
|
|
19
|
+
[[ -z "$name" ]] && continue
|
|
20
|
+
if [[ "$name" != "$current" && "$name" != "$base" ]] && printf '%s\n' "$merged" | grep -qxF -- "$name"; then
|
|
21
|
+
printf 'merged: %s\tlocal\t%s\n' "$name" "$name"
|
|
22
|
+
else
|
|
23
|
+
printf 'local: %s\tlocal\t%s\n' "$name" "$name"
|
|
24
|
+
fi
|
|
25
|
+
done <<< "$local_names"
|
|
26
|
+
|
|
27
|
+
# Remote branches without a local branch of the same name
|
|
28
|
+
local printed_spacer=false
|
|
29
|
+
while IFS= read -r name; do
|
|
30
|
+
# Skip symbolic refs like "origin" (origin/HEAD)
|
|
31
|
+
[[ -z "$name" || "$name" != */* || "$name" == */HEAD ]] && continue
|
|
32
|
+
remote_name="${name#*/}"
|
|
33
|
+
printf '%s\n' "$local_names" | grep -qxF -- "$remote_name" && continue
|
|
34
|
+
if [[ "$printed_spacer" == false ]]; then
|
|
35
|
+
printf '─────────────────────────────\tspacer\t\n'
|
|
36
|
+
printed_spacer=true
|
|
37
|
+
fi
|
|
38
|
+
printf 'remote: %s\tremote\t%s\n' "$name" "$name"
|
|
39
|
+
done < <(git for-each-ref --format='%(refname:short)' refs/remotes)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Delete the local branch of a list line (called from fzf via 'switch --delete-branch')
|
|
43
|
+
_switch_delete_branch() {
|
|
44
|
+
local line="$1" type name current count
|
|
45
|
+
type=$(printf '%s' "$line" | cut -f2)
|
|
46
|
+
name=$(printf '%s' "$line" | cut -f3)
|
|
47
|
+
current=$(gb_current_branch) || current=""
|
|
48
|
+
|
|
49
|
+
_switch_pause() { prompt_read "Press Enter to continue..." _ || true; }
|
|
50
|
+
|
|
51
|
+
if [[ "$type" != "local" ]]; then
|
|
52
|
+
echo "Only local branches can be deleted here. Use 'stale' to delete remote branches."
|
|
53
|
+
_switch_pause
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
if [[ "$name" == "$current" ]]; then
|
|
57
|
+
print_warning "'$name' is the current branch - switch to another branch first."
|
|
58
|
+
_switch_pause
|
|
18
59
|
return 0
|
|
19
60
|
fi
|
|
20
|
-
if
|
|
21
|
-
|
|
61
|
+
if gb_is_protected "$name"; then
|
|
62
|
+
print_warning "'$name' is protected (GITBASH_PROTECTED_BRANCHES)."
|
|
63
|
+
_switch_pause
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
gb_confirm --strict "Delete local branch '$name'?" n || return 0
|
|
68
|
+
if git branch -d "$name" >/dev/null 2>&1; then
|
|
69
|
+
print_success "Deleted $name"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
count=$(git rev-list --count "refs/heads/$name" --not --remotes "$(gb_base_ref "$(gb_base_branch 2>/dev/null || echo HEAD)")" 2>/dev/null || echo "?")
|
|
74
|
+
print_warning "'$name' has $count commit(s) that are not merged or pushed:"
|
|
75
|
+
git log --oneline -n 10 "refs/heads/$name" --not --remotes 2>/dev/null | sed 's/^/ /'
|
|
76
|
+
if gb_confirm --strict "Force-delete '$name' and lose these commits?" n; then
|
|
77
|
+
if git branch -D "$name" >/dev/null 2>&1; then
|
|
78
|
+
print_success "Force-deleted $name"
|
|
79
|
+
else
|
|
80
|
+
print_error "Failed to delete $name"
|
|
81
|
+
_switch_pause
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Select and switch to a branch using fzf
|
|
87
|
+
switch() {
|
|
88
|
+
# Internal entry points used by fzf bindings
|
|
89
|
+
case "${1:-}" in
|
|
90
|
+
--list-branches)
|
|
91
|
+
_switch_list_branches
|
|
92
|
+
return 0
|
|
93
|
+
;;
|
|
94
|
+
--delete-branch)
|
|
95
|
+
_switch_delete_branch "${2:-}"
|
|
96
|
+
return 0
|
|
97
|
+
;;
|
|
98
|
+
-v|--version)
|
|
99
|
+
echo "gitbash ${FUNCNAME[0]} v$VERSION"
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
-h|--help)
|
|
103
|
+
cat << 'EOF'
|
|
22
104
|
Usage: switch [FILTER...]
|
|
23
105
|
|
|
24
106
|
Select a git branch using fzf and switch to it.
|
|
25
107
|
|
|
26
108
|
Arguments:
|
|
27
|
-
FILTER... Optional search
|
|
109
|
+
FILTER... Optional search words to pre-fill fzf (joined with spaces).
|
|
110
|
+
If exactly one branch name contains them, switches directly.
|
|
28
111
|
|
|
29
112
|
Options:
|
|
30
113
|
-h, --help Show this help message
|
|
31
114
|
|
|
32
115
|
Behavior:
|
|
33
|
-
- Lists
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
116
|
+
- Lists local branches ("merged:" = fully merged into the base branch)
|
|
117
|
+
and remote branches that have no local branch yet
|
|
118
|
+
- For remote branches, creates a local tracking branch
|
|
119
|
+
- After switching, offers to fast-forward if the branch is behind its upstream
|
|
120
|
+
|
|
121
|
+
Keys:
|
|
122
|
+
Enter Switch to the selected branch
|
|
123
|
+
Del Delete the selected local branch (asks first, default: no;
|
|
124
|
+
unmerged commits are shown and need a second confirmation)
|
|
125
|
+
Esc Exit
|
|
39
126
|
|
|
40
127
|
Examples:
|
|
41
128
|
$ switch
|
|
42
|
-
# Opens fzf with list of branches, select one to switch
|
|
43
|
-
|
|
44
129
|
$ switch captcha
|
|
45
|
-
# Opens fzf with 'captcha' pre-filled as filter
|
|
46
|
-
|
|
47
130
|
$ switch LOVE-123
|
|
48
|
-
# Opens fzf filtering for branches containing 'LOVE-123'
|
|
49
|
-
|
|
50
|
-
$ switch update figma guidelines
|
|
51
|
-
# Opens fzf with 'update figma guidelines' pre-filled as filter
|
|
52
131
|
|
|
53
132
|
Requirements:
|
|
54
133
|
- Must be in a git repository
|
|
55
134
|
- fzf must be installed
|
|
56
135
|
|
|
57
136
|
EOF
|
|
58
|
-
|
|
59
|
-
|
|
137
|
+
return 0
|
|
138
|
+
;;
|
|
139
|
+
esac
|
|
60
140
|
|
|
61
|
-
# -----------------------------
|
|
62
|
-
# 1. Check prerequisites
|
|
63
|
-
# -----------------------------
|
|
64
141
|
require_git_repo || return 1
|
|
65
142
|
require_fzf || return 1
|
|
66
143
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# -----------------------------
|
|
70
|
-
local current_branch
|
|
71
|
-
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
72
|
-
|
|
73
|
-
local filter="$*"
|
|
144
|
+
local current_branch filter="$*"
|
|
145
|
+
current_branch=$(gb_current_branch) || current_branch="(detached HEAD)"
|
|
74
146
|
|
|
75
|
-
# -----------------------------
|
|
76
|
-
# 3. List branches and select with fzf
|
|
77
|
-
# -----------------------------
|
|
78
|
-
# Get local branches
|
|
79
|
-
local local_branches
|
|
80
|
-
local_branches=$(git branch --format='%(refname:short)')
|
|
81
|
-
# Get merged local branches (merged into HEAD) in a portable way
|
|
82
|
-
local merged_local_branches
|
|
83
|
-
merged_local_branches=$(git branch --merged | sed 's/^..//' | sed '/^$/d' || true)
|
|
84
|
-
|
|
85
|
-
# Get remote branches, filter out those that already exist locally
|
|
86
|
-
local remote_branches
|
|
87
|
-
remote_branches=$(git branch -r --format='%(refname:short)' | grep -v 'HEAD' | grep -v '^origin$' | while read -r remote; do
|
|
88
|
-
local_name="${remote#*/}" # Remove origin/ prefix
|
|
89
|
-
# Only include if no local branch with this name exists
|
|
90
|
-
if ! echo "$local_branches" | grep -qx "$local_name"; then
|
|
91
|
-
echo "$remote"
|
|
92
|
-
fi
|
|
93
|
-
done || true)
|
|
94
|
-
|
|
95
|
-
local selected_branch
|
|
96
147
|
local branch_list
|
|
97
|
-
branch_list=$(
|
|
98
|
-
|
|
99
|
-
# List local branches, marking merged ones as 'merged:' (but keep current branch as local)
|
|
100
|
-
echo "$local_branches" | while IFS= read -r lb; do
|
|
101
|
-
# Skip empty lines
|
|
102
|
-
[[ -z "$lb" ]] && continue
|
|
103
|
-
if [[ "$lb" == "$current_branch" ]]; then
|
|
104
|
-
echo "local: $lb"
|
|
105
|
-
elif [[ "$lb" == "master" || "$lb" == "main" ]]; then
|
|
106
|
-
# Keep main branches labelled as local
|
|
107
|
-
echo "local: $lb"
|
|
108
|
-
elif echo "$merged_local_branches" | grep -xq -- "${lb}" 2>/dev/null; then
|
|
109
|
-
echo "merged: $lb"
|
|
110
|
-
else
|
|
111
|
-
echo "local: $lb"
|
|
112
|
-
fi
|
|
113
|
-
done
|
|
114
|
-
# Spacer (only if there are remote branches to show)
|
|
115
|
-
if [[ -n "$remote_branches" ]]; then
|
|
116
|
-
echo "─────────────────────────────"
|
|
117
|
-
# List filtered remote branches
|
|
118
|
-
echo "$remote_branches" | sed 's/^/remote: /'
|
|
119
|
-
fi
|
|
120
|
-
}
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# If no branches were found, bail out to avoid fzf listing files
|
|
124
|
-
if [[ -z "$(echo "$branch_list" | sed -n '1p' || true)" ]]; then
|
|
148
|
+
branch_list=$(_switch_list_branches)
|
|
149
|
+
if [[ -z "$branch_list" ]]; then
|
|
125
150
|
echo "No branches found in this repository."
|
|
126
151
|
return 0
|
|
127
152
|
fi
|
|
128
153
|
|
|
129
154
|
# -----------------------------
|
|
130
|
-
#
|
|
155
|
+
# Single match: switch directly
|
|
131
156
|
# -----------------------------
|
|
157
|
+
local selected="" query="$filter"
|
|
132
158
|
if [[ -n "$filter" ]]; then
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
[[ -z "$match_count" ]] && match_count=0
|
|
140
|
-
|
|
159
|
+
local matches match_count
|
|
160
|
+
matches=$(printf '%s\n' "$branch_list" | awk -F'\t' '$2 != "spacer"' |
|
|
161
|
+
while IFS= read -r line; do
|
|
162
|
+
printf '%s\n' "$line" | cut -f3 | grep -qiF -- "$filter" && printf '%s\n' "$line"
|
|
163
|
+
done)
|
|
164
|
+
match_count=$(printf '%s' "$matches" | grep -c . || true)
|
|
141
165
|
if [[ "$match_count" == "1" ]]; then
|
|
142
|
-
|
|
143
|
-
selected_branch="$matching_branches"
|
|
166
|
+
selected="$matches"
|
|
144
167
|
print_info "Single match found, switching directly..."
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if [[ "$match_count" == "0" ]]; then
|
|
148
|
-
filter_for_fzf=""
|
|
149
|
-
else
|
|
150
|
-
filter_for_fzf="$filter"
|
|
168
|
+
elif [[ "$match_count" == "0" ]]; then
|
|
169
|
+
query=""
|
|
151
170
|
fi
|
|
152
171
|
fi
|
|
153
172
|
|
|
154
173
|
# -----------------------------
|
|
155
|
-
#
|
|
174
|
+
# fzf picker
|
|
156
175
|
# -----------------------------
|
|
157
|
-
if [[ -z "$
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
break
|
|
185
|
-
;;
|
|
186
|
-
[nN]|[nN][oO])
|
|
187
|
-
echo "Aborted deletion of $branch"
|
|
188
|
-
exit 0
|
|
189
|
-
;;
|
|
190
|
-
*)
|
|
191
|
-
echo "Please answer y or n" > /dev/tty
|
|
192
|
-
;;
|
|
193
|
-
esac
|
|
194
|
-
done
|
|
195
|
-
|
|
196
|
-
if git branch -D "$branch"; then
|
|
197
|
-
echo "Deleted local $branch"
|
|
198
|
-
awk -v l="$line" '$0!=l' "$branches_file" > "$branches_file".new && mv "$branches_file".new "$branches_file"
|
|
199
|
-
else
|
|
200
|
-
echo "Failed to delete $branch"
|
|
176
|
+
if [[ -z "$selected" ]]; then
|
|
177
|
+
local bin_q
|
|
178
|
+
printf -v bin_q '%q' "${GITBASH_BIN:-gitbash}"
|
|
179
|
+
selected=$(run_fzf \
|
|
180
|
+
--height=40% \
|
|
181
|
+
--reverse \
|
|
182
|
+
--border \
|
|
183
|
+
-i \
|
|
184
|
+
--query="$query" \
|
|
185
|
+
--delimiter=$'\t' \
|
|
186
|
+
--with-nth=1 \
|
|
187
|
+
--bind="del:execute($bin_q switch --delete-branch {} < /dev/tty > /dev/tty 2>&1)+reload($bin_q switch --list-branches)" \
|
|
188
|
+
--preview="
|
|
189
|
+
type=\$(printf '%s' {} | cut -f2); name=\$(printf '%s' {} | cut -f3)
|
|
190
|
+
case \"\$type\" in
|
|
191
|
+
local) ref=\"refs/heads/\$name\" ;;
|
|
192
|
+
remote) ref=\"refs/remotes/\$name\" ;;
|
|
193
|
+
*) echo 'Spacer - not selectable'; exit 0 ;;
|
|
194
|
+
esac
|
|
195
|
+
git log --color=always -n 1 --format='%C(bold cyan)Author:%C(reset) %an%n%C(bold cyan)Date:%C(reset) %ar (%ad)%n%C(bold cyan)Message:%C(reset) %s%n' --date=format:'%Y-%m-%d %H:%M' \"\$ref\" 2>/dev/null
|
|
196
|
+
echo
|
|
197
|
+
git log --oneline --color=always -n 10 \"\$ref\" 2>/dev/null
|
|
198
|
+
" \
|
|
199
|
+
--preview-window=right:50% \
|
|
200
|
+
--header="[Enter] switch | [Del] delete local branch | [Esc] exit | Current: $current_branch" \
|
|
201
|
+
<<< "$branch_list"
|
|
202
|
+
) || true
|
|
201
203
|
fi
|
|
202
|
-
else
|
|
203
|
-
echo "Remote branch deletion is disabled in this UI. Delete remote branches manually with: git push <remote> --delete <branch>"
|
|
204
|
-
exit 0
|
|
205
|
-
fi
|
|
206
|
-
DEL_SH
|
|
207
|
-
chmod +x "$delete_script"
|
|
208
|
-
|
|
209
|
-
# Loop running fzf so we can handle delete keys via --expect and reload after deletion
|
|
210
|
-
selected_branch=""
|
|
211
|
-
while true; do
|
|
212
|
-
fzf_out=$(
|
|
213
|
-
fzf \
|
|
214
|
-
--height=40% \
|
|
215
|
-
--reverse \
|
|
216
|
-
--border \
|
|
217
|
-
--expect=enter,delete \
|
|
218
|
-
--query="${filter_for_fzf:-$filter}" \
|
|
219
|
-
-i \
|
|
220
|
-
--preview="branch=\$(echo {} | sed 's/^[^:]*: //'); if [[ \"\$branch\" == *───* ]]; then echo 'Spacer - not selectable'; else git log --color=always -n 1 --format='%C(bold cyan)Author:%C(reset) %an%n%C(bold cyan)Date:%C(reset) %ar (%ad)%n%C(bold cyan)Message:%C(reset) %s%n' --date=format:'%Y-%m-%d %H:%M' \"\$branch\" 2>/dev/null && echo && git log --oneline --color=always -n 10 \"\$branch\" 2>/dev/null; fi" \
|
|
221
|
-
--preview-window=right:50% \
|
|
222
|
-
--header="[Enter] switch | [Del] delete (local only) | [Esc] exit | Current: $current_branch" \
|
|
223
|
-
< "$branches_file"
|
|
224
|
-
) </dev/tty || true
|
|
225
|
-
|
|
226
|
-
key_pressed=$(echo "$fzf_out" | head -n1)
|
|
227
|
-
selection=$(echo "$fzf_out" | tail -n +2 | head -n1)
|
|
228
|
-
|
|
229
|
-
# No selection or escape
|
|
230
|
-
if [[ -z "$key_pressed" && -z "$selection" ]]; then
|
|
231
|
-
selected_branch=""
|
|
232
|
-
break
|
|
233
|
-
fi
|
|
234
204
|
|
|
235
|
-
|
|
236
|
-
selected_branch="$selection"
|
|
237
|
-
break
|
|
238
|
-
fi
|
|
239
|
-
|
|
240
|
-
if [[ "$key_pressed" == "delete" ]]; then
|
|
241
|
-
# perform deletion on the selected line and continue loop
|
|
242
|
-
if [[ -n "$selection" ]]; then
|
|
243
|
-
"$delete_script" "$selection" "$branches_file"
|
|
244
|
-
fi
|
|
245
|
-
continue
|
|
246
|
-
fi
|
|
247
|
-
|
|
248
|
-
# If there is a selection without a key, treat as accept
|
|
249
|
-
if [[ -n "$selection" ]]; then
|
|
250
|
-
selected_branch="$selection"
|
|
251
|
-
break
|
|
252
|
-
fi
|
|
253
|
-
done
|
|
254
|
-
|
|
255
|
-
# Clean up temp files
|
|
256
|
-
rm -f "$delete_script" "$branches_file"
|
|
257
|
-
fi
|
|
258
|
-
|
|
259
|
-
# -----------------------------
|
|
260
|
-
# 4. Handle selection
|
|
261
|
-
# -----------------------------
|
|
262
|
-
if [[ -z "$selected_branch" ]]; then
|
|
205
|
+
if [[ -z "$selected" ]]; then
|
|
263
206
|
echo "No branch selected."
|
|
264
207
|
return 0
|
|
265
208
|
fi
|
|
266
209
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
210
|
+
local branch_type branch_name
|
|
211
|
+
branch_type=$(printf '%s' "$selected" | cut -f2)
|
|
212
|
+
branch_name=$(printf '%s' "$selected" | cut -f3)
|
|
213
|
+
# Only accept well-formed list lines ("<label>\t<type>\t<name>")
|
|
214
|
+
if [[ ( "$branch_type" != "local" && "$branch_type" != "remote" ) || -z "$branch_name" ]]; then
|
|
215
|
+
echo "No branch selected."
|
|
270
216
|
return 0
|
|
271
217
|
fi
|
|
272
218
|
|
|
273
|
-
# Extract branch type and name
|
|
274
|
-
local branch_type="${selected_branch%%:*}"
|
|
275
|
-
local branch_name="${selected_branch#*: }"
|
|
276
|
-
|
|
277
219
|
# -----------------------------
|
|
278
|
-
#
|
|
220
|
+
# Switch
|
|
279
221
|
# -----------------------------
|
|
280
|
-
local
|
|
281
|
-
|
|
282
|
-
# Switch to local branch
|
|
283
|
-
echo "Switching to local branch: $branch_name"
|
|
284
|
-
if git checkout "$branch_name"; then
|
|
285
|
-
switch_success=true
|
|
286
|
-
fi
|
|
222
|
+
if [[ "$branch_type" == "local" ]]; then
|
|
223
|
+
git switch "$branch_name" || { print_error "Failed to switch branch."; return 1; }
|
|
287
224
|
else
|
|
288
|
-
|
|
289
|
-
local local_branch="${branch_name#*/}" # Remove origin/ prefix
|
|
290
|
-
|
|
291
|
-
# Check if local branch already exists
|
|
225
|
+
local local_branch="${branch_name#*/}"
|
|
292
226
|
if git show-ref --verify --quiet "refs/heads/$local_branch"; then
|
|
293
|
-
|
|
294
|
-
if git checkout "$local_branch"; then
|
|
295
|
-
switch_success=true
|
|
296
|
-
fi
|
|
227
|
+
git switch "$local_branch" || { print_error "Failed to switch branch."; return 1; }
|
|
297
228
|
else
|
|
298
|
-
echo "Creating local
|
|
299
|
-
|
|
300
|
-
switch_success=true
|
|
301
|
-
fi
|
|
229
|
+
echo "Creating local branch '$local_branch' tracking '$branch_name'"
|
|
230
|
+
git switch -c "$local_branch" --track "$branch_name" || { print_error "Failed to switch branch."; return 1; }
|
|
302
231
|
fi
|
|
303
232
|
fi
|
|
233
|
+
print_success "Switched to branch: $(gb_current_branch)"
|
|
304
234
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
print_warning "Branch is $behind_count commit(s) behind '$upstream'"
|
|
323
|
-
local pull_answer
|
|
324
|
-
prompt_read "Pull latest changes? (Y/n): " pull_answer
|
|
325
|
-
case "$pull_answer" in
|
|
326
|
-
[nN][oO]|[nN])
|
|
327
|
-
print_info "Skipped pulling. Run 'git pull' when ready."
|
|
328
|
-
;;
|
|
329
|
-
*)
|
|
330
|
-
if git pull; then
|
|
331
|
-
print_success "Successfully pulled latest changes."
|
|
332
|
-
else
|
|
333
|
-
print_error "Pull failed. You may need to resolve conflicts."
|
|
334
|
-
return 1
|
|
335
|
-
fi
|
|
336
|
-
;;
|
|
337
|
-
esac
|
|
235
|
+
# -----------------------------
|
|
236
|
+
# Offer to fast-forward if behind the upstream
|
|
237
|
+
# -----------------------------
|
|
238
|
+
local now upstream_remote upstream_merge behind
|
|
239
|
+
now=$(gb_current_branch) || return 0
|
|
240
|
+
upstream_remote=$(git config "branch.$now.remote" 2>/dev/null) || return 0
|
|
241
|
+
upstream_merge=$(git config "branch.$now.merge" 2>/dev/null) || return 0
|
|
242
|
+
git fetch --quiet "$upstream_remote" "$upstream_merge" 2>/dev/null || return 0
|
|
243
|
+
behind=$(git rev-list --count "HEAD..@{upstream}" 2>/dev/null) || return 0
|
|
244
|
+
if [[ "$behind" -gt 0 ]]; then
|
|
245
|
+
print_warning "Branch is $behind commit(s) behind its upstream."
|
|
246
|
+
if gb_confirm "Fast-forward to the latest changes?" y; then
|
|
247
|
+
if git merge --ff-only --quiet "@{upstream}"; then
|
|
248
|
+
print_success "Up to date."
|
|
249
|
+
else
|
|
250
|
+
print_error "Could not fast-forward (you have local commits or changes). Run 'git pull' to reconcile."
|
|
251
|
+
return 1
|
|
338
252
|
fi
|
|
339
253
|
fi
|
|
340
|
-
else
|
|
341
|
-
print_error "Failed to switch branch."
|
|
342
|
-
return 1
|
|
343
254
|
fi
|
|
255
|
+
return 0
|
|
344
256
|
}
|