gitbash 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -35
- package/bin/gitbash +145 -18
- package/commands/_self_update.sh +323 -0
- package/commands/_utils.sh +153 -9
- package/commands/branch.sh +3 -3
- package/commands/cleanstash.sh +1 -1
- package/commands/cleanup.sh +3 -3
- package/commands/commit.sh +1 -1
- package/commands/commits.sh +59 -10
- package/commands/create.sh +1 -1
- package/commands/pr.sh +3 -49
- package/commands/repo.sh +82 -0
- package/commands/reset-repo.sh +200 -0
- package/commands/stale.sh +2 -2
- package/commands/stash.sh +1 -1
- package/commands/stashes.sh +2 -2
- package/commands/status.sh +3 -3
- package/commands/switch.sh +16 -25
- package/commands/unstash.sh +6 -4
- package/commands/update.sh +1 -1
- package/docs/CONTRIBUTING.md +2 -1
- package/docs/SECURITY.md +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-update for gitbash: 'gitbash --update' and the background update check.
|
|
3
|
+
# Sourced by bin/gitbash; needs SCRIPT_DIR, GITBASH_BIN and VERSION.
|
|
4
|
+
|
|
5
|
+
GB_REPO="jaggli/gitbash"
|
|
6
|
+
GB_UPDATE_CHECK_INTERVAL=86400 # look for a new version at most once a day
|
|
7
|
+
GB_UPDATE_PROMPT_INTERVAL=172800 # ask about it at most every other day
|
|
8
|
+
|
|
9
|
+
# GITBASH_NO_UPDATE_CHECKS from the environment wins over the config files
|
|
10
|
+
_GB_ENV_NO_UPDATE_CHECKS="${GITBASH_NO_UPDATE_CHECKS:-}"
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# Install method
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
# Directory of a global package manager install of gitbash, symlinks resolved
|
|
17
|
+
_gb_global_package_dir() {
|
|
18
|
+
local root
|
|
19
|
+
root=$("$1" root -g 2>/dev/null) || return 1
|
|
20
|
+
[[ -n "$root" ]] || return 1
|
|
21
|
+
# Windows: C:\Users\...\npm\node_modules
|
|
22
|
+
if gb_is_windows && command -v cygpath >/dev/null 2>&1; then
|
|
23
|
+
root=$(cygpath -u "$root") || return 1
|
|
24
|
+
fi
|
|
25
|
+
(cd -P "$root/gitbash" 2>/dev/null && pwd)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# How this copy of gitbash was installed: npm, pnpm, npx, package, script, git or unknown
|
|
29
|
+
gb_install_method() {
|
|
30
|
+
case "$SCRIPT_DIR" in
|
|
31
|
+
*/_npx/*)
|
|
32
|
+
echo "npx"
|
|
33
|
+
return 0
|
|
34
|
+
;;
|
|
35
|
+
*/node_modules/gitbash)
|
|
36
|
+
local pm
|
|
37
|
+
for pm in npm pnpm; do
|
|
38
|
+
if command -v "$pm" >/dev/null 2>&1 &&
|
|
39
|
+
[[ "$(_gb_global_package_dir "$pm")" == "$SCRIPT_DIR" ]]; then
|
|
40
|
+
echo "$pm"
|
|
41
|
+
return 0
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
# A dependency of some project, or a package manager we don't know
|
|
45
|
+
echo "package"
|
|
46
|
+
return 0
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
if [[ -f "$SCRIPT_DIR/.gitbash-install" ]]; then
|
|
50
|
+
echo "script"
|
|
51
|
+
elif [[ -e "$SCRIPT_DIR/.git" ]]; then
|
|
52
|
+
echo "git"
|
|
53
|
+
elif [[ -f "$SCRIPT_DIR/package.json" && ! -e "$SCRIPT_DIR/README.md" ]]; then
|
|
54
|
+
# Installed by an install.sh that did not leave a marker yet
|
|
55
|
+
echo "script"
|
|
56
|
+
else
|
|
57
|
+
echo "unknown"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# =============================================================================
|
|
62
|
+
# State (~/.local/state/gitbash/update-check)
|
|
63
|
+
# =============================================================================
|
|
64
|
+
|
|
65
|
+
_gb_update_state_file() {
|
|
66
|
+
echo "${XDG_STATE_HOME:-$HOME/.local/state}/gitbash/update-check"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Read the state into _GB_UC_CHECKED, _GB_UC_LATEST, _GB_UC_PROMPTED and _GB_UC_SKIPPED
|
|
70
|
+
_gb_update_state_load() {
|
|
71
|
+
local file key value
|
|
72
|
+
file=$(_gb_update_state_file)
|
|
73
|
+
_GB_UC_CHECKED=0 _GB_UC_LATEST="" _GB_UC_PROMPTED=0 _GB_UC_SKIPPED=""
|
|
74
|
+
[[ -f "$file" ]] || return 0
|
|
75
|
+
while IFS='=' read -r key value; do
|
|
76
|
+
case "$key" in
|
|
77
|
+
checked) [[ "$value" =~ ^[0-9]+$ ]] && _GB_UC_CHECKED="$value" ;;
|
|
78
|
+
latest) _GB_UC_LATEST="$value" ;;
|
|
79
|
+
prompted) [[ "$value" =~ ^[0-9]+$ ]] && _GB_UC_PROMPTED="$value" ;;
|
|
80
|
+
skipped) _GB_UC_SKIPPED="$value" ;;
|
|
81
|
+
esac
|
|
82
|
+
done < "$file"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Set one value, keeping the others (re-read first: a background check may have written)
|
|
86
|
+
# Usage: _gb_update_state_set checked|latest|prompted|skipped <value>
|
|
87
|
+
_gb_update_state_set() {
|
|
88
|
+
local file dir tmp
|
|
89
|
+
file=$(_gb_update_state_file)
|
|
90
|
+
dir=$(dirname "$file")
|
|
91
|
+
_gb_update_state_load
|
|
92
|
+
case "$1" in
|
|
93
|
+
checked) _GB_UC_CHECKED="$2" ;;
|
|
94
|
+
latest) _GB_UC_LATEST="$2" ;;
|
|
95
|
+
prompted) _GB_UC_PROMPTED="$2" ;;
|
|
96
|
+
skipped) _GB_UC_SKIPPED="$2" ;;
|
|
97
|
+
esac
|
|
98
|
+
mkdir -p "$dir" 2>/dev/null || return 1
|
|
99
|
+
tmp=$(mktemp "$dir/.update-check.XXXXXX" 2>/dev/null) || return 1
|
|
100
|
+
if ! printf 'checked=%s\nlatest=%s\nprompted=%s\nskipped=%s\n' \
|
|
101
|
+
"$_GB_UC_CHECKED" "$_GB_UC_LATEST" "$_GB_UC_PROMPTED" "$_GB_UC_SKIPPED" > "$tmp" ||
|
|
102
|
+
! mv -f "$tmp" "$file"; then
|
|
103
|
+
rm -f "$tmp"
|
|
104
|
+
return 1
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_gb_now() {
|
|
109
|
+
if [[ -n "${EPOCHSECONDS:-}" ]]; then
|
|
110
|
+
echo "$EPOCHSECONDS"
|
|
111
|
+
else
|
|
112
|
+
date +%s
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# =============================================================================
|
|
117
|
+
# Versions
|
|
118
|
+
# =============================================================================
|
|
119
|
+
|
|
120
|
+
# Print the contents of a URL
|
|
121
|
+
_gb_fetch() {
|
|
122
|
+
if command -v curl >/dev/null 2>&1; then
|
|
123
|
+
curl -fsSL --max-time 10 "$1"
|
|
124
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
125
|
+
wget -qO- -T 10 "$1"
|
|
126
|
+
else
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Print the version of the latest GitHub release, e.g. 2.1.0
|
|
132
|
+
gb_latest_version() {
|
|
133
|
+
local json tag
|
|
134
|
+
json=$(_gb_fetch "https://api.github.com/repos/$GB_REPO/releases/latest" 2>/dev/null) || return 1
|
|
135
|
+
tag=$(printf '%s\n' "$json" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
|
|
136
|
+
tag="${tag#v}"
|
|
137
|
+
[[ "$tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
|
138
|
+
echo "$tag"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Returns 0 if version $1 is newer than the running one
|
|
142
|
+
_gb_is_newer() {
|
|
143
|
+
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && ! _gb_version_ge "$VERSION" "$1"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# =============================================================================
|
|
147
|
+
# Update
|
|
148
|
+
# =============================================================================
|
|
149
|
+
|
|
150
|
+
# Install version $1 the way this copy was installed
|
|
151
|
+
gb_self_update_to() {
|
|
152
|
+
local latest="$1" method
|
|
153
|
+
method=$(gb_install_method)
|
|
154
|
+
case "$method" in
|
|
155
|
+
npm|pnpm)
|
|
156
|
+
local -a cmd=(npm install -g "gitbash@$latest")
|
|
157
|
+
[[ "$method" == "pnpm" ]] && cmd=(pnpm add -g "gitbash@$latest")
|
|
158
|
+
print_info "Running: ${cmd[*]}"
|
|
159
|
+
if ! "${cmd[@]}"; then
|
|
160
|
+
print_error "Update failed. Run '${cmd[*]}' yourself (it may need more permissions)."
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
163
|
+
;;
|
|
164
|
+
script)
|
|
165
|
+
_gb_self_update_script "$latest" || return 1
|
|
166
|
+
;;
|
|
167
|
+
npx)
|
|
168
|
+
print_info "gitbash runs through npx: use 'npx gitbash@latest' to get the newest version."
|
|
169
|
+
return 1
|
|
170
|
+
;;
|
|
171
|
+
git)
|
|
172
|
+
print_info "gitbash runs from a git checkout ($SCRIPT_DIR): update it with 'git pull'."
|
|
173
|
+
return 1
|
|
174
|
+
;;
|
|
175
|
+
package)
|
|
176
|
+
print_info "gitbash is a dependency in $SCRIPT_DIR: update it with your package manager."
|
|
177
|
+
return 1
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
print_error "Could not detect how gitbash was installed ($SCRIPT_DIR)."
|
|
181
|
+
echo "Reinstall it with 'npm i -g gitbash' or the install script (see the README)." >&2
|
|
182
|
+
return 1
|
|
183
|
+
;;
|
|
184
|
+
esac
|
|
185
|
+
|
|
186
|
+
# GITBASH_BIN, not SCRIPT_DIR: pnpm installs the new version in a new folder
|
|
187
|
+
local installed pkg_dir="${GITBASH_BIN%/bin/gitbash}"
|
|
188
|
+
installed=$(grep -o '"version": *"[^"]*"' "$pkg_dir/package.json" 2>/dev/null | head -1 | cut -d'"' -f4)
|
|
189
|
+
if [[ "$installed" != "$latest" ]]; then
|
|
190
|
+
print_error "Update finished, but gitbash in $pkg_dir is still at ${installed:-an unknown version}."
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
print_success "Updated gitbash $VERSION → $latest"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Re-run the install script of the new release for the same locations
|
|
197
|
+
_gb_self_update_script() {
|
|
198
|
+
local latest="$1" bin_dir="" key value tmp
|
|
199
|
+
if [[ -f "$SCRIPT_DIR/.gitbash-install" ]]; then
|
|
200
|
+
while IFS='=' read -r key value; do
|
|
201
|
+
[[ "$key" == "bin_dir" ]] && bin_dir="$value"
|
|
202
|
+
done < "$SCRIPT_DIR/.gitbash-install"
|
|
203
|
+
fi
|
|
204
|
+
bin_dir="${bin_dir:-$HOME/.local/bin}"
|
|
205
|
+
|
|
206
|
+
local -a vars=(GITBASH_VERSION="$latest" GITBASH_INSTALL_DIR="$SCRIPT_DIR" GITBASH_BIN_DIR="$bin_dir")
|
|
207
|
+
local hint="curl -fsSL https://raw.githubusercontent.com/$GB_REPO/main/install.sh | sudo ${vars[*]} sh"
|
|
208
|
+
if [[ ! -w "$(dirname "$SCRIPT_DIR")" ]]; then
|
|
209
|
+
print_error "No permission to write to $(dirname "$SCRIPT_DIR"). Update with:"
|
|
210
|
+
echo " $hint" >&2
|
|
211
|
+
return 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
tmp=$(mktemp 2>/dev/null || mktemp -t gitbash) || return 1
|
|
215
|
+
if ! _gb_fetch "https://raw.githubusercontent.com/$GB_REPO/v$latest/install.sh" > "$tmp" 2>/dev/null ||
|
|
216
|
+
[[ ! -s "$tmp" ]]; then
|
|
217
|
+
rm -f "$tmp"
|
|
218
|
+
print_error "Could not download the install script of gitbash $latest."
|
|
219
|
+
return 1
|
|
220
|
+
fi
|
|
221
|
+
print_info "Running the install script of gitbash $latest"
|
|
222
|
+
if ! env "${vars[@]}" sh "$tmp"; then
|
|
223
|
+
rm -f "$tmp"
|
|
224
|
+
print_error "Update failed."
|
|
225
|
+
return 1
|
|
226
|
+
fi
|
|
227
|
+
rm -f "$tmp"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# gitbash --update
|
|
231
|
+
gb_self_update() {
|
|
232
|
+
local latest
|
|
233
|
+
print_info "Checking for updates..."
|
|
234
|
+
if ! latest=$(gb_latest_version); then
|
|
235
|
+
print_error "Could not check for updates (no network, or GitHub is not reachable)."
|
|
236
|
+
return 1
|
|
237
|
+
fi
|
|
238
|
+
_gb_update_state_set checked "$(_gb_now)"
|
|
239
|
+
_gb_update_state_set latest "$latest"
|
|
240
|
+
if ! _gb_is_newer "$latest"; then
|
|
241
|
+
print_success "gitbash $VERSION is up to date."
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
gb_self_update_to "$latest"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# =============================================================================
|
|
248
|
+
# Background check
|
|
249
|
+
# =============================================================================
|
|
250
|
+
|
|
251
|
+
# Returns 0 if update checks are on: not turned off, not in CI, not a git checkout
|
|
252
|
+
gb_update_checks_enabled() {
|
|
253
|
+
case "$_GB_ENV_NO_UPDATE_CHECKS" in
|
|
254
|
+
"") [[ "${GITBASH_NO_UPDATE_CHECKS:-no}" == "yes" ]] && return 1 ;;
|
|
255
|
+
no|0|false) ;;
|
|
256
|
+
*) return 1 ;;
|
|
257
|
+
esac
|
|
258
|
+
local var
|
|
259
|
+
for var in CI CONTINUOUS_INTEGRATION BUILD_NUMBER GITHUB_ACTIONS GITLAB_CI TF_BUILD \
|
|
260
|
+
JENKINS_URL BUILDKITE CIRCLECI TRAVIS TEAMCITY_VERSION BITBUCKET_BUILD_NUMBER; do
|
|
261
|
+
case "${!var:-}" in
|
|
262
|
+
""|false|0) ;;
|
|
263
|
+
*) return 1 ;;
|
|
264
|
+
esac
|
|
265
|
+
done
|
|
266
|
+
[[ ! -e "$SCRIPT_DIR/.git" ]]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Run before a command: start a background check when one is due, and offer a
|
|
270
|
+
# known new version (at most every other day, never again for a skipped one).
|
|
271
|
+
# On a successful update, runs the command with the new version instead.
|
|
272
|
+
# Usage: gb_update_check <command> [args...]
|
|
273
|
+
gb_update_check() {
|
|
274
|
+
# Not when gitbash runs itself from a command (the parent already checked)
|
|
275
|
+
[[ -z "${GITBASH_NESTED:-}" ]] || return 0
|
|
276
|
+
gb_update_checks_enabled || return 0
|
|
277
|
+
|
|
278
|
+
local now
|
|
279
|
+
now=$(_gb_now)
|
|
280
|
+
_gb_update_state_load
|
|
281
|
+
if (( now - _GB_UC_CHECKED >= GB_UPDATE_CHECK_INTERVAL || _GB_UC_CHECKED > now )); then
|
|
282
|
+
_gb_update_state_set checked "$now"
|
|
283
|
+
(
|
|
284
|
+
local latest
|
|
285
|
+
latest=$(gb_latest_version) && _gb_update_state_set latest "$latest"
|
|
286
|
+
) < /dev/null > /dev/null 2>&1 &
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
local latest="$_GB_UC_LATEST"
|
|
290
|
+
_gb_is_newer "$latest" || return 0
|
|
291
|
+
[[ "$latest" != "$_GB_UC_SKIPPED" ]] || return 0
|
|
292
|
+
(( now - _GB_UC_PROMPTED >= GB_UPDATE_PROMPT_INTERVAL || _GB_UC_PROMPTED > now )) || return 0
|
|
293
|
+
# Only ask people at a terminal, never scripts
|
|
294
|
+
[[ -t 0 && -t 1 && -t 2 && "${GITBASH_ASSUME_YES:-}" != "1" ]] || return 0
|
|
295
|
+
local arg
|
|
296
|
+
for arg in "${@:2}"; do
|
|
297
|
+
[[ "$arg" == "-y" || "$arg" == "--yes" ]] && return 0
|
|
298
|
+
done
|
|
299
|
+
|
|
300
|
+
_gb_update_state_set prompted "$now"
|
|
301
|
+
print_info "gitbash $latest is available (you have $VERSION)."
|
|
302
|
+
local choice
|
|
303
|
+
gb_choice choice "Update now? [y]es, [n]ot now, [s]kip this version (y/N/s):" "yns" "n"
|
|
304
|
+
case "$choice" in
|
|
305
|
+
y)
|
|
306
|
+
if gb_self_update_to "$latest"; then
|
|
307
|
+
echo
|
|
308
|
+
exec "$GITBASH_BIN" "$@"
|
|
309
|
+
fi
|
|
310
|
+
echo
|
|
311
|
+
;;
|
|
312
|
+
s)
|
|
313
|
+
_gb_update_state_set skipped "$latest"
|
|
314
|
+
print_info "You won't be asked about $latest again. Run 'gitbash --update' to update any time."
|
|
315
|
+
echo
|
|
316
|
+
;;
|
|
317
|
+
*)
|
|
318
|
+
print_info "Run 'gitbash --update' to update any time."
|
|
319
|
+
echo
|
|
320
|
+
;;
|
|
321
|
+
esac
|
|
322
|
+
return 0
|
|
323
|
+
}
|
package/commands/_utils.sh
CHANGED
|
@@ -9,7 +9,29 @@ if [[ -n "${_GB_UTILS_LOADED:-}" ]]; then
|
|
|
9
9
|
fi
|
|
10
10
|
_GB_UTILS_LOADED=1
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# Platform
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
# Git Bash (MSYS2) or Cygwin on Windows
|
|
17
|
+
gb_is_windows() {
|
|
18
|
+
case "${OSTYPE:-}" in
|
|
19
|
+
msys*|cygwin*) return 0 ;;
|
|
20
|
+
esac
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Linux in WSL
|
|
25
|
+
_gb_is_wsl() {
|
|
26
|
+
[[ -n "${WSL_DISTRO_NAME:-}" || -n "${WSL_INTEROP:-}" ]]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# On Windows, fzf runs previews with bash and quotes {} for it since 0.54
|
|
30
|
+
if gb_is_windows; then
|
|
31
|
+
GB_FZF_MIN_VERSION="0.54.0"
|
|
32
|
+
else
|
|
33
|
+
GB_FZF_MIN_VERSION="0.36.0"
|
|
34
|
+
fi
|
|
13
35
|
|
|
14
36
|
# =============================================================================
|
|
15
37
|
# Output
|
|
@@ -20,6 +42,21 @@ _gb_color_ok() {
|
|
|
20
42
|
[[ -z "${NO_COLOR:-}" && -t "$1" ]]
|
|
21
43
|
}
|
|
22
44
|
|
|
45
|
+
# NO_COLOR (https://no-color.org): a non-empty value turns off all colors.
|
|
46
|
+
# GB_COLOR is the --color value for git and bat in fzf previews; it is
|
|
47
|
+
# exported because previews run in a separate bash started by fzf.
|
|
48
|
+
if [[ -n "${NO_COLOR:-}" ]]; then
|
|
49
|
+
GB_COLOR="never"
|
|
50
|
+
# Also for git's own output (added to any GIT_CONFIG_* entries already set)
|
|
51
|
+
_gb_n="${GIT_CONFIG_COUNT:-0}"
|
|
52
|
+
export "GIT_CONFIG_KEY_$_gb_n=color.ui" "GIT_CONFIG_VALUE_$_gb_n=never"
|
|
53
|
+
export GIT_CONFIG_COUNT=$((_gb_n + 1))
|
|
54
|
+
unset _gb_n
|
|
55
|
+
else
|
|
56
|
+
GB_COLOR="always"
|
|
57
|
+
fi
|
|
58
|
+
export GB_COLOR
|
|
59
|
+
|
|
23
60
|
_gb_print() {
|
|
24
61
|
local fd="$1" color="$2" symbol="$3"
|
|
25
62
|
shift 3
|
|
@@ -35,6 +72,26 @@ print_info() { _gb_print 1 "0;34" "ℹ" "$@"; }
|
|
|
35
72
|
print_warning() { _gb_print 2 "0;33" "⚠" "$@"; }
|
|
36
73
|
print_error() { _gb_print 2 "0;31" "✗" "$@"; }
|
|
37
74
|
|
|
75
|
+
# Print help text from stdin, coloring the section headings
|
|
76
|
+
# ("Options:", "See also:", ...) and the "Usage:" label.
|
|
77
|
+
gb_help() {
|
|
78
|
+
if ! _gb_color_ok 1; then
|
|
79
|
+
cat
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
local h=$'\033[1;38;5;209m' r=$'\033[0m' line
|
|
83
|
+
local heading='^[A-Z][A-Za-z ()]*:$'
|
|
84
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
85
|
+
if [[ "$line" =~ $heading ]]; then
|
|
86
|
+
printf '%s%s%s\n' "$h" "$line" "$r"
|
|
87
|
+
elif [[ "$line" == "Usage: "* ]]; then
|
|
88
|
+
printf '%sUsage:%s%s\n' "$h" "$r" "${line#Usage:}"
|
|
89
|
+
else
|
|
90
|
+
printf '%s\n' "$line"
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
}
|
|
94
|
+
|
|
38
95
|
# =============================================================================
|
|
39
96
|
# Prompts
|
|
40
97
|
# =============================================================================
|
|
@@ -101,7 +158,7 @@ gb_choice() {
|
|
|
101
158
|
GB_CONFIG_KEYS="GITBASH_CREATE_BRANCH_PREFIX GITBASH_FEATURE_BRANCH_PREFIX GITBASH_MERGE_COMMAND \
|
|
102
159
|
GITBASH_CREATE_NO_ISSUE_PARSING GITBASH_CREATE_ISSUE_PARSING_FALLBACK GITBASH_CREATE_AUTO_PUSH \
|
|
103
160
|
GITBASH_THEME GITBASH_STALE_MONTHS GITBASH_CLEANUP_DAYS GITBASH_PROTECTED_BRANCHES \
|
|
104
|
-
GITBASH_BASE_BRANCH GITBASH_REMOTE"
|
|
161
|
+
GITBASH_BASE_BRANCH GITBASH_REMOTE GITBASH_NO_UPDATE_CHECKS GITBASH_RESET_KEEP"
|
|
105
162
|
|
|
106
163
|
_gb_config_key_allowed() {
|
|
107
164
|
[[ " $GB_CONFIG_KEYS " == *" $1 "* ]]
|
|
@@ -179,6 +236,7 @@ _gb_normalize_config() {
|
|
|
179
236
|
GITBASH_CREATE_ISSUE_PARSING_FALLBACK="${GITBASH_CREATE_ISSUE_PARSING_FALLBACK:-NOISSUE}"
|
|
180
237
|
GITBASH_PROTECTED_BRANCHES="${GITBASH_PROTECTED_BRANCHES:-main master develop release/*}"
|
|
181
238
|
GITBASH_BASE_BRANCH="${GITBASH_BASE_BRANCH:-}"
|
|
239
|
+
GITBASH_RESET_KEEP="${GITBASH_RESET_KEEP:-}"
|
|
182
240
|
|
|
183
241
|
case "${GITBASH_CREATE_NO_ISSUE_PARSING:-no}" in
|
|
184
242
|
yes|no) GITBASH_CREATE_NO_ISSUE_PARSING="${GITBASH_CREATE_NO_ISSUE_PARSING:-no}" ;;
|
|
@@ -195,6 +253,12 @@ _gb_normalize_config() {
|
|
|
195
253
|
*) print_warning "Invalid GITBASH_THEME '$GITBASH_THEME', using 'auto'."
|
|
196
254
|
GITBASH_THEME="auto" ;;
|
|
197
255
|
esac
|
|
256
|
+
case "${GITBASH_NO_UPDATE_CHECKS:-no}" in
|
|
257
|
+
yes|1|true) GITBASH_NO_UPDATE_CHECKS="yes" ;;
|
|
258
|
+
no|0|false) GITBASH_NO_UPDATE_CHECKS="no" ;;
|
|
259
|
+
*) print_warning "Invalid GITBASH_NO_UPDATE_CHECKS '$GITBASH_NO_UPDATE_CHECKS', using 'no'."
|
|
260
|
+
GITBASH_NO_UPDATE_CHECKS="no" ;;
|
|
261
|
+
esac
|
|
198
262
|
if ! [[ "${GITBASH_STALE_MONTHS:-3}" =~ ^[1-9][0-9]*$ ]]; then
|
|
199
263
|
print_warning "Invalid GITBASH_STALE_MONTHS '$GITBASH_STALE_MONTHS', using 3."
|
|
200
264
|
GITBASH_STALE_MONTHS=3
|
|
@@ -244,7 +308,11 @@ _gb_version_ge() {
|
|
|
244
308
|
# Check that fzf is installed and recent enough
|
|
245
309
|
require_fzf() {
|
|
246
310
|
if ! command -v fzf >/dev/null 2>&1; then
|
|
247
|
-
|
|
311
|
+
if gb_is_windows; then
|
|
312
|
+
print_error "fzf is not installed. Install it with 'winget install junegunn.fzf'."
|
|
313
|
+
else
|
|
314
|
+
print_error "fzf is not installed. Install it with 'brew install fzf' (macOS) or 'sudo apt install fzf' (Debian/Ubuntu)."
|
|
315
|
+
fi
|
|
248
316
|
return 1
|
|
249
317
|
fi
|
|
250
318
|
local version
|
|
@@ -310,6 +378,29 @@ gb_base_ref() {
|
|
|
310
378
|
fi
|
|
311
379
|
}
|
|
312
380
|
|
|
381
|
+
# Fetch the current branch's upstream and, if it has new commits, offer to
|
|
382
|
+
# fast-forward. Returns 1 only when the fast-forward fails.
|
|
383
|
+
gb_offer_fast_forward() {
|
|
384
|
+
local branch upstream_remote upstream_merge behind
|
|
385
|
+
branch=$(gb_current_branch) || return 0
|
|
386
|
+
upstream_remote=$(git config "branch.$branch.remote" 2>/dev/null) || return 0
|
|
387
|
+
upstream_merge=$(git config "branch.$branch.merge" 2>/dev/null) || return 0
|
|
388
|
+
git fetch --quiet "$upstream_remote" "$upstream_merge" 2>/dev/null || return 0
|
|
389
|
+
behind=$(git rev-list --count "HEAD..@{upstream}" 2>/dev/null) || return 0
|
|
390
|
+
if [[ "$behind" -gt 0 ]]; then
|
|
391
|
+
print_warning "Branch is $behind commit(s) behind its upstream."
|
|
392
|
+
if gb_confirm "Fast-forward to the latest changes?" y; then
|
|
393
|
+
if git merge --ff-only --quiet "@{upstream}"; then
|
|
394
|
+
print_success "Up to date."
|
|
395
|
+
else
|
|
396
|
+
print_error "Could not fast-forward (you have local commits or changes). Run 'git pull' to reconcile."
|
|
397
|
+
return 1
|
|
398
|
+
fi
|
|
399
|
+
fi
|
|
400
|
+
fi
|
|
401
|
+
return 0
|
|
402
|
+
}
|
|
403
|
+
|
|
313
404
|
# Returns 0 if the branch is protected (base branch or GITBASH_PROTECTED_BRANCHES).
|
|
314
405
|
# Set GB_BASE beforehand to avoid detecting the base branch on every call.
|
|
315
406
|
gb_is_protected() {
|
|
@@ -454,9 +545,9 @@ gb_sync_and_push() {
|
|
|
454
545
|
# Previews
|
|
455
546
|
# =============================================================================
|
|
456
547
|
|
|
457
|
-
# Command that colors a diff read from stdin (delta if installed)
|
|
548
|
+
# Command that colors a diff read from stdin (delta if installed and colors are on)
|
|
458
549
|
gb_diff_pager() {
|
|
459
|
-
if command -v delta >/dev/null 2>&1; then
|
|
550
|
+
if [[ "$GB_COLOR" == "always" ]] && command -v delta >/dev/null 2>&1; then
|
|
460
551
|
case "${GITBASH_THEME:-auto}" in
|
|
461
552
|
dark) echo "delta --dark" ;;
|
|
462
553
|
light) echo "delta --light" ;;
|
|
@@ -480,9 +571,9 @@ gb_bat_cmd() {
|
|
|
480
571
|
return 0
|
|
481
572
|
fi
|
|
482
573
|
case "${GITBASH_THEME:-auto}" in
|
|
483
|
-
dark) echo "$bat --color
|
|
484
|
-
light) echo "$bat --color
|
|
485
|
-
*) echo "$bat --color
|
|
574
|
+
dark) echo "$bat --color=$GB_COLOR --style=numbers --theme=Dracula" ;;
|
|
575
|
+
light) echo "$bat --color=$GB_COLOR --style=numbers --theme=GitHub" ;;
|
|
576
|
+
*) echo "$bat --color=$GB_COLOR --style=numbers" ;;
|
|
486
577
|
esac
|
|
487
578
|
}
|
|
488
579
|
|
|
@@ -518,10 +609,63 @@ gb_urlencode() {
|
|
|
518
609
|
printf '%s' "$out"
|
|
519
610
|
}
|
|
520
611
|
|
|
612
|
+
# Convert a remote URL to the repository's web URL (credentials and .git removed).
|
|
613
|
+
# Handles https://[user[:token]@]host/path, ssh://[user@]host[:port]/path and user@host:path.
|
|
614
|
+
gb_web_url() {
|
|
615
|
+
local url="$1" scheme="https" rest host path
|
|
616
|
+
case "$url" in
|
|
617
|
+
http://*|https://*)
|
|
618
|
+
scheme="${url%%://*}"
|
|
619
|
+
rest="${url#*://}"
|
|
620
|
+
host="${rest%%/*}"
|
|
621
|
+
host="${host##*@}"
|
|
622
|
+
path="${rest#*/}"
|
|
623
|
+
;;
|
|
624
|
+
ssh://*)
|
|
625
|
+
rest="${url#ssh://}"
|
|
626
|
+
host="${rest%%/*}"
|
|
627
|
+
host="${host##*@}"
|
|
628
|
+
host="${host%%:*}"
|
|
629
|
+
path="${rest#*/}"
|
|
630
|
+
;;
|
|
631
|
+
*@*:*)
|
|
632
|
+
host="${url%%:*}"
|
|
633
|
+
host="${host##*@}"
|
|
634
|
+
path="${url#*:}"
|
|
635
|
+
;;
|
|
636
|
+
*)
|
|
637
|
+
return 1
|
|
638
|
+
;;
|
|
639
|
+
esac
|
|
640
|
+
path="${path%/}"
|
|
641
|
+
path="${path%.git}"
|
|
642
|
+
if [[ -z "$host" || -z "$path" || ( -n "$rest" && "$path" == "$rest" ) ]]; then
|
|
643
|
+
return 1
|
|
644
|
+
fi
|
|
645
|
+
|
|
646
|
+
# Azure DevOps SSH: ssh.dev.azure.com:v3/org/project/repo
|
|
647
|
+
if [[ "$host" == "ssh.dev.azure.com" && "$path" == v3/*/*/* ]]; then
|
|
648
|
+
local org project repo
|
|
649
|
+
path="${path#v3/}"
|
|
650
|
+
org="${path%%/*}"; path="${path#*/}"
|
|
651
|
+
project="${path%%/*}"; repo="${path#*/}"
|
|
652
|
+
echo "https://dev.azure.com/$org/$project/_git/$repo"
|
|
653
|
+
return 0
|
|
654
|
+
fi
|
|
655
|
+
echo "$scheme://$host/$path"
|
|
656
|
+
}
|
|
657
|
+
|
|
521
658
|
# Open a URL in the browser, or print it if no opener is available
|
|
522
659
|
gb_open_url() {
|
|
523
660
|
local url="$1"
|
|
524
|
-
if command -v
|
|
661
|
+
if gb_is_windows && command -v rundll32 >/dev/null 2>&1; then
|
|
662
|
+
# Not cmd's start: it would need & and ^ in the URL escaped
|
|
663
|
+
rundll32 url.dll,FileProtocolHandler "$url"
|
|
664
|
+
elif _gb_is_wsl && command -v wslview >/dev/null 2>&1; then
|
|
665
|
+
wslview "$url"
|
|
666
|
+
elif _gb_is_wsl && command -v rundll32.exe >/dev/null 2>&1; then
|
|
667
|
+
rundll32.exe url.dll,FileProtocolHandler "$url"
|
|
668
|
+
elif command -v open >/dev/null 2>&1 && [[ "$(uname -s)" == "Darwin" ]]; then
|
|
525
669
|
open "$url"
|
|
526
670
|
elif command -v xdg-open >/dev/null 2>&1; then
|
|
527
671
|
xdg-open "$url" >/dev/null 2>&1
|
package/commands/branch.sh
CHANGED
|
@@ -14,7 +14,7 @@ branch() {
|
|
|
14
14
|
return 0
|
|
15
15
|
fi
|
|
16
16
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
17
|
-
|
|
17
|
+
gb_help << 'EOF'
|
|
18
18
|
Usage: branch [OPTIONS]
|
|
19
19
|
|
|
20
20
|
Interactive menu to manage git branches.
|
|
@@ -86,8 +86,8 @@ Examples:
|
|
|
86
86
|
✓ 'feature/PROJ-123-add-new-feature' is now up-to-date with 'main'.
|
|
87
87
|
|
|
88
88
|
Actions:
|
|
89
|
-
🌿 Create
|
|
90
|
-
🔀 Switch
|
|
89
|
+
🌿 Create - Create a new feature branch with Jira parsing
|
|
90
|
+
🔀 Switch - Switch to another branch using fzf
|
|
91
91
|
⬆️ Update - Update current branch with latest main/master
|
|
92
92
|
|
|
93
93
|
Requirements:
|
package/commands/cleanstash.sh
CHANGED
package/commands/cleanup.sh
CHANGED
|
@@ -37,7 +37,7 @@ cleanup() {
|
|
|
37
37
|
return 0
|
|
38
38
|
;;
|
|
39
39
|
-h|--help)
|
|
40
|
-
|
|
40
|
+
gb_help << 'EOF'
|
|
41
41
|
Usage: cleanup [OPTIONS]
|
|
42
42
|
|
|
43
43
|
Find and delete local branches that are no longer needed.
|
|
@@ -273,14 +273,14 @@ ${#sorted[@]} branches, $preselect_count pre-selected" \
|
|
|
273
273
|
if [[ -z "$branch" ]]; then echo "Exit without action"; exit 0; fi
|
|
274
274
|
echo "Branch: $branch"
|
|
275
275
|
echo
|
|
276
|
-
unpushed=$(git log --oneline --color
|
|
276
|
+
unpushed=$(git log --oneline --color=$GB_COLOR "refs/heads/$branch" --not --remotes 2>/dev/null)
|
|
277
277
|
if [[ -n "$unpushed" ]]; then
|
|
278
278
|
echo "Unpushed commits (only on this machine):"
|
|
279
279
|
echo "$unpushed"
|
|
280
280
|
echo
|
|
281
281
|
fi
|
|
282
282
|
echo "Recent commits:"
|
|
283
|
-
git log --oneline --color
|
|
283
|
+
git log --oneline --color=$GB_COLOR -n 15 "refs/heads/$branch" 2>/dev/null
|
|
284
284
|
' \
|
|
285
285
|
--preview-window=right:40% \
|
|
286
286
|
--bind "load:$toggle_sequence" \
|