gitbash 2.0.0 → 2.1.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 +64 -17
- package/bin/gitbash +58 -10
- package/commands/_self_update.sh +319 -0
- package/commands/_utils.sh +117 -6
- package/commands/branch.sh +3 -3
- package/commands/cleanstash.sh +1 -1
- package/commands/cleanup.sh +4 -4
- package/commands/commit.sh +1 -1
- package/commands/commits.sh +59 -10
- package/commands/create.sh +1 -1
- package/commands/pr.sh +2 -48
- package/commands/repo.sh +82 -0
- package/commands/reset-repo.sh +200 -0
- package/commands/stale.sh +4 -4
- 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 +1 -0
- package/docs/SECURITY.md +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,11 +12,35 @@ Pure bash, zero-dependency git utilities. Reduce repetitive typing and superchar
|
|
|
12
12
|
npm i -g gitbash && gitbash --config
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
### Via install script (no npm or node needed)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# curl
|
|
19
|
+
curl -fsSL https://raw.githubusercontent.com/jaggli/gitbash/main/install.sh | sh
|
|
20
|
+
|
|
21
|
+
# wget
|
|
22
|
+
wget -qO- https://raw.githubusercontent.com/jaggli/gitbash/main/install.sh | sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This downloads the latest GitHub release to `~/.local/share/gitbash` and links `~/.local/bin/gitbash`. Run `gitbash --update` (or the script again) to upgrade. Options are set as environment variables:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Install a specific version
|
|
29
|
+
curl -fsSL https://raw.githubusercontent.com/jaggli/gitbash/main/install.sh | GITBASH_VERSION=2.0.1 sh
|
|
30
|
+
|
|
31
|
+
# Custom locations (e.g. system-wide)
|
|
32
|
+
curl -fsSL https://raw.githubusercontent.com/jaggli/gitbash/main/install.sh \
|
|
33
|
+
| sudo GITBASH_INSTALL_DIR=/usr/local/share/gitbash GITBASH_BIN_DIR=/usr/local/bin sh
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To uninstall, remove `~/.local/share/gitbash` and `~/.local/bin/gitbash`.
|
|
37
|
+
|
|
15
38
|
### Options
|
|
16
39
|
|
|
17
40
|
```bash
|
|
18
41
|
gitbash --help # Show help
|
|
19
42
|
gitbash --version # Show version
|
|
43
|
+
gitbash --update # Update to the latest release (npm or install script)
|
|
20
44
|
gitbash --init # Print shell functions (see Shell integration)
|
|
21
45
|
gitbash --config # Interactive configuration wizard
|
|
22
46
|
gitbash --config-local # Configure overrides for the current repository (committed)
|
|
@@ -46,18 +70,6 @@ eval "$(gitbash --init --prefix=gb-)" # gb-commit, gb-switch, ... (avoids shadow
|
|
|
46
70
|
|
|
47
71
|
This defines small wrapper functions; every command still runs in its own bash process, so nothing else is added to your shell. Individual aliases work too, e.g. `alias commit="gitbash commit"`.
|
|
48
72
|
|
|
49
|
-
### Upgrading to 2.0
|
|
50
|
-
|
|
51
|
-
2.0 is a security and safety release. What changes for you:
|
|
52
|
-
|
|
53
|
-
- **Restart your shell** after upgrading, so `eval "$(gitbash --init)"` picks up the new wrapper functions.
|
|
54
|
-
- Config files are **read, not executed**: only plain `GITBASH_*="value"` lines are used, other lines are ignored with a warning. `GITBASH_MERGE_COMMAND` is ignored in a committed `.gitbashrc`.
|
|
55
|
-
- `status` no longer commits and pushes after staging. Use `Ctrl-O` to commit.
|
|
56
|
-
- `stale` toggles all/stale branches with `Ctrl-T` (was `Ctrl-A`).
|
|
57
|
-
- Protected branches (base branch and `GITBASH_PROTECTED_BRANCHES`) are never offered for deletion. `cleanup` and `switch` delete with `git branch -d` and ask again before force-deleting unmerged work.
|
|
58
|
-
- When your branch and the remote diverged, `commit -p`, `pr -p` and `update -p` ask whether to rebase, merge or abort instead of rebasing silently.
|
|
59
|
-
- fzf 0.36 or newer is required for the interactive menus.
|
|
60
|
-
|
|
61
73
|
## Commands
|
|
62
74
|
|
|
63
75
|
### branch
|
|
@@ -115,7 +127,7 @@ create enhance login screen # → feature/enhance-login-screen (pars
|
|
|
115
127
|
switch [FILTER...]
|
|
116
128
|
```
|
|
117
129
|
|
|
118
|
-
Switch branches with fzf. Shows local branches (`merged:` = fully merged into the base branch), then remote branches without a local copy. Preview shows commit history.
|
|
130
|
+
Switch branches with fzf. Shows local branches (`merged:` = fully merged into the base branch), then remote branches without a local copy. Preview shows commit history. Switches directly when the filter is an exact branch name or matches exactly one branch name. After switching, offers to fast-forward a branch that is behind.
|
|
119
131
|
|
|
120
132
|
- `Del` deletes the selected local branch (asks first, default no; unmerged commits need a second confirmation)
|
|
121
133
|
|
|
@@ -162,6 +174,22 @@ pr [-p|--push] [--print]
|
|
|
162
174
|
|
|
163
175
|
Open the pull request in the browser: the existing PR via the GitHub CLI when available, otherwise the create page (GitHub, GitHub Enterprise, GitLab, Bitbucket, Azure DevOps). Offers to commit local changes and to push a branch that is not on the remote yet. `-p` pushes first, `--print` prints the URL.
|
|
164
176
|
|
|
177
|
+
### repo
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
repo [--print]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Open the repository's web page in the browser: via the GitHub CLI (`gh repo view --web`) when available, otherwise the URL derived from the remote (GitHub, GitHub Enterprise, GitLab, Bitbucket, Azure DevOps). `--print` prints the URL.
|
|
184
|
+
|
|
185
|
+
### reset-repo
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
reset-repo [-n|--dry-run] [-y|--yes]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Reset the current branch like a fresh clone: fetch, `git reset --hard <remote>/<branch>` and delete all untracked and ignored files (`git clean -dx`). Lists the unpushed commits, local changes and files it removes and asks first. Aborts an unfinished merge or rebase. Files matching `GITBASH_RESET_KEEP` (e.g. `.env`), `.gitbashrc-user` and nested repositories are kept; stashes and other branches are not touched.
|
|
192
|
+
|
|
165
193
|
### update
|
|
166
194
|
|
|
167
195
|
```bash
|
|
@@ -238,14 +266,15 @@ Unpushed commits are shown per branch. Deletes with `git branch -d`; force-delet
|
|
|
238
266
|
### commits
|
|
239
267
|
|
|
240
268
|
```bash
|
|
241
|
-
commits [COUNT]
|
|
269
|
+
commits [-a|--all] [COUNT]
|
|
242
270
|
```
|
|
243
271
|
|
|
244
|
-
List
|
|
272
|
+
List the current branch's own commits with option to revert. On a feature branch only commits not in the base branch are shown; on the base branch its first-parent history (merged branches as their merge commit). If the upstream has new commits, offers to fast-forward first. Multi-select with TAB; selected commits are reverted newest first.
|
|
245
273
|
|
|
246
274
|
```bash
|
|
247
|
-
commits
|
|
248
|
-
commits 50
|
|
275
|
+
commits # Show the branch's last 20 commits
|
|
276
|
+
commits 50 # Show up to 50
|
|
277
|
+
commits --all # All recent commits, like git log
|
|
249
278
|
```
|
|
250
279
|
|
|
251
280
|
## Configuration
|
|
@@ -278,11 +307,17 @@ GITBASH_CLEANUP_DAYS="7"
|
|
|
278
307
|
# Branches never offered for deletion, space-separated globs
|
|
279
308
|
GITBASH_PROTECTED_BRANCHES="main master develop release/*"
|
|
280
309
|
|
|
310
|
+
# Files 'reset-repo' keeps, space-separated .gitignore patterns, e.g. ".env .idea/" (default: "")
|
|
311
|
+
GITBASH_RESET_KEEP=""
|
|
312
|
+
|
|
281
313
|
# Base branch (default: detected from origin/HEAD, then main, then master)
|
|
282
314
|
GITBASH_BASE_BRANCH=""
|
|
283
315
|
|
|
284
316
|
# Remote name (default: "origin")
|
|
285
317
|
GITBASH_REMOTE="origin"
|
|
318
|
+
|
|
319
|
+
# Turn off the automatic update checks: yes or no (default: "no")
|
|
320
|
+
GITBASH_NO_UPDATE_CHECKS="no"
|
|
286
321
|
```
|
|
287
322
|
|
|
288
323
|
Config files are **read, never executed**. Only `GITBASH_*="value"` lines are used (double, single or no quotes, optional `export`, `# comments`). Values cannot contain `$`, backticks, backslashes or quotes; anything else is ignored with a warning.
|
|
@@ -301,6 +336,18 @@ Run `gitbash --config-user` to create `.gitbashrc-user` for personal overrides i
|
|
|
301
336
|
2. `.gitbashrc` - Repository settings (committed)
|
|
302
337
|
3. `~/.gitbashrc` - Global settings
|
|
303
338
|
|
|
339
|
+
#### Updates
|
|
340
|
+
|
|
341
|
+
`gitbash --update` installs the latest release the same way gitbash was installed: with `npm install -g` (or `pnpm add -g`) for a global package, or by running the release's install script again for the same locations.
|
|
342
|
+
|
|
343
|
+
gitbash also checks for new releases on its own, at most once a day, in the background while a command runs, so commands never wait for the network. When a new version is known, the next command at a terminal asks (at most every other day) whether to update now, not now, or skip that version for good. It never asks in scripts or pipes.
|
|
344
|
+
|
|
345
|
+
To turn the checks off, answer "no" in `gitbash --config` or `--config-user`, set `GITBASH_NO_UPDATE_CHECKS="yes"` in any config file, or export `GITBASH_NO_UPDATE_CHECKS=1`. They are also off in CI (`CI` and similar variables) and when running from a git checkout. The check state is kept in `~/.local/state/gitbash/update-check` (`$XDG_STATE_HOME` if set).
|
|
346
|
+
|
|
347
|
+
#### Colors
|
|
348
|
+
|
|
349
|
+
gitbash supports [`NO_COLOR`](https://no-color.org): set it to any non-empty value (e.g. `export NO_COLOR=1`) to turn off all colors, including the help, messages, the git output in previews and `bat`. `delta` is not used then, and fzf uses its black-and-white theme. Colors are also left out when the output is not a terminal.
|
|
350
|
+
|
|
304
351
|
## License
|
|
305
352
|
|
|
306
353
|
See [LICENSE](LICENSE) file for details.
|
package/bin/gitbash
CHANGED
|
@@ -10,12 +10,20 @@ while [[ -L "$SOURCE" ]]; do
|
|
|
10
10
|
done
|
|
11
11
|
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
12
12
|
GITBASH_BIN="$SCRIPT_DIR/bin/gitbash"
|
|
13
|
+
# pnpm keeps each version in its own folder (node_modules/.pnpm/gitbash@X/node_modules/gitbash):
|
|
14
|
+
# run gitbash through the node_modules/gitbash link instead, which follows updates
|
|
15
|
+
if [[ "$SCRIPT_DIR" == */node_modules/.pnpm/gitbash@*/node_modules/gitbash &&
|
|
16
|
+
-x "${SCRIPT_DIR%%/.pnpm/*}/gitbash/bin/gitbash" ]]; then
|
|
17
|
+
GITBASH_BIN="${SCRIPT_DIR%%/.pnpm/*}/gitbash/bin/gitbash"
|
|
18
|
+
fi
|
|
13
19
|
COMMANDS_DIR="$SCRIPT_DIR/commands"
|
|
14
20
|
VERSION="$(grep -o '"version": *"[^"]*"' "$SCRIPT_DIR/package.json" | head -1 | cut -d'"' -f4)"
|
|
15
21
|
export SCRIPT_DIR GITBASH_BIN VERSION
|
|
16
22
|
|
|
17
23
|
# shellcheck source=../commands/_utils.sh
|
|
18
24
|
source "$COMMANDS_DIR/_utils.sh"
|
|
25
|
+
# shellcheck source=../commands/_self_update.sh
|
|
26
|
+
source "$COMMANDS_DIR/_self_update.sh"
|
|
19
27
|
|
|
20
28
|
# Available commands
|
|
21
29
|
COMMANDS=(
|
|
@@ -26,6 +34,8 @@ COMMANDS=(
|
|
|
26
34
|
"commits"
|
|
27
35
|
"create"
|
|
28
36
|
"pr"
|
|
37
|
+
"repo"
|
|
38
|
+
"reset-repo"
|
|
29
39
|
"stale"
|
|
30
40
|
"stash"
|
|
31
41
|
"stashes"
|
|
@@ -50,7 +60,7 @@ is_command() {
|
|
|
50
60
|
# key|type|question
|
|
51
61
|
SETTINGS=(
|
|
52
62
|
"GITBASH_CREATE_BRANCH_PREFIX|prefix|Branch prefix inserted between type and branch name (e.g. 'team' → feature/team/PROJ-123-fix-bug)"
|
|
53
|
-
"GITBASH_CREATE_NO_ISSUE_PARSING|
|
|
63
|
+
"GITBASH_CREATE_NO_ISSUE_PARSING|inverted|Parse Jira issue numbers into branch names? (yes/no)"
|
|
54
64
|
"GITBASH_CREATE_ISSUE_PARSING_FALLBACK|text|Fallback issue prefix when no issue number is given"
|
|
55
65
|
"GITBASH_CREATE_AUTO_PUSH|yesno|Push new branches to the remote right after 'create'? (yes/no)"
|
|
56
66
|
"GITBASH_MERGE_COMMAND|command|Merge tool opened by 'update' on conflicts (not allowed in the committed .gitbashrc)"
|
|
@@ -58,8 +68,10 @@ SETTINGS=(
|
|
|
58
68
|
"GITBASH_STALE_MONTHS|number|Stale remote branch threshold in months (used by 'stale')"
|
|
59
69
|
"GITBASH_CLEANUP_DAYS|number|Stale local branch threshold in days (used by 'cleanup')"
|
|
60
70
|
"GITBASH_PROTECTED_BRANCHES|text|Branches never offered for deletion (space-separated globs)"
|
|
71
|
+
"GITBASH_RESET_KEEP|text|Files kept by 'reset-repo' (space-separated .gitignore patterns, e.g. '.env .idea/')"
|
|
61
72
|
"GITBASH_BASE_BRANCH|text|Base branch (empty = detect from the remote's HEAD, then main/master)"
|
|
62
73
|
"GITBASH_REMOTE|text|Remote name"
|
|
74
|
+
"GITBASH_NO_UPDATE_CHECKS|inverted|Check for gitbash updates now and then, and offer to install them? (yes/no)"
|
|
63
75
|
)
|
|
64
76
|
|
|
65
77
|
# Default value of a setting
|
|
@@ -74,6 +86,7 @@ setting_default() {
|
|
|
74
86
|
GITBASH_CLEANUP_DAYS) echo "7" ;;
|
|
75
87
|
GITBASH_PROTECTED_BRANCHES) echo "main master develop release/*" ;;
|
|
76
88
|
GITBASH_REMOTE) echo "origin" ;;
|
|
89
|
+
GITBASH_NO_UPDATE_CHECKS) echo "no" ;;
|
|
77
90
|
*) echo "" ;;
|
|
78
91
|
esac
|
|
79
92
|
}
|
|
@@ -91,10 +104,10 @@ file_value() {
|
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
# Convert between the stored value and what the wizard shows.
|
|
94
|
-
# GITBASH_CREATE_NO_ISSUE_PARSING
|
|
107
|
+
# GITBASH_CREATE_NO_ISSUE_PARSING and GITBASH_NO_UPDATE_CHECKS are stored inverted.
|
|
95
108
|
to_display() {
|
|
96
109
|
local type="$1" value="$2"
|
|
97
|
-
if [[ "$type" == "
|
|
110
|
+
if [[ "$type" == "inverted" ]]; then
|
|
98
111
|
[[ "$value" == "yes" ]] && echo "no" || echo "yes"
|
|
99
112
|
else
|
|
100
113
|
printf '%s' "$value"
|
|
@@ -109,7 +122,7 @@ to_stored() {
|
|
|
109
122
|
return 1
|
|
110
123
|
fi
|
|
111
124
|
case "$type" in
|
|
112
|
-
|
|
125
|
+
inverted)
|
|
113
126
|
case "$answer" in
|
|
114
127
|
yes) echo "no" ;;
|
|
115
128
|
no) echo "yes" ;;
|
|
@@ -183,7 +196,9 @@ configure_wizard() {
|
|
|
183
196
|
for entry in "${SETTINGS[@]}"; do
|
|
184
197
|
n=$((n + 1))
|
|
185
198
|
IFS='|' read -r key type question <<< "$entry"
|
|
186
|
-
[[ "$scope" == "local"
|
|
199
|
+
if [[ "$scope" == "local" ]]; then
|
|
200
|
+
[[ "$key" == "GITBASH_MERGE_COMMAND" || "$key" == "GITBASH_NO_UPDATE_CHECKS" ]] && continue
|
|
201
|
+
fi
|
|
187
202
|
|
|
188
203
|
default=$(setting_default "$key")
|
|
189
204
|
has_override=false
|
|
@@ -356,13 +371,37 @@ check_shell_integration() {
|
|
|
356
371
|
# Help and shell integration
|
|
357
372
|
# =============================================================================
|
|
358
373
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
374
|
+
# ASCII art logo with a color gradient, followed by the version.
|
|
375
|
+
# Plain text when stdout is not a terminal or NO_COLOR is set.
|
|
376
|
+
show_banner() {
|
|
377
|
+
local -a art=(
|
|
378
|
+
' _ _ _ _ '
|
|
379
|
+
' __ _(_) |_| |__ __ _ ___| |__ '
|
|
380
|
+
' / _` | | __| '"'"'_ \ / _` / __| '"'"'_ \ '
|
|
381
|
+
' | (_| | | |_| |_) | (_| \__ \ | | |'
|
|
382
|
+
' \__, |_|\__|_.__/ \__,_|___/_| |_|'
|
|
383
|
+
' |___/ '
|
|
384
|
+
)
|
|
385
|
+
local -a colors=(214 209 204 199 170 135)
|
|
386
|
+
local i
|
|
387
|
+
if _gb_color_ok 1; then
|
|
388
|
+
for i in "${!art[@]}"; do
|
|
389
|
+
printf '\033[1;38;5;%sm%s\033[0m\n' "${colors[$i]}" "${art[$i]}"
|
|
390
|
+
done
|
|
391
|
+
printf ' \033[1;38;5;135mv%s\033[0m \033[2m·\033[0m Interactive git utilities for bash\n' "$VERSION"
|
|
392
|
+
else
|
|
393
|
+
printf '%s\n' "${art[@]}"
|
|
394
|
+
printf ' v%s - Interactive git utilities for bash\n' "$VERSION"
|
|
395
|
+
fi
|
|
396
|
+
}
|
|
362
397
|
|
|
398
|
+
show_help() {
|
|
399
|
+
show_banner
|
|
400
|
+
echo
|
|
401
|
+
gb_help << EOF
|
|
363
402
|
Usage:
|
|
364
403
|
gitbash <command> [args...]
|
|
365
|
-
gitbash --help | --version
|
|
404
|
+
gitbash --help | --version | --update
|
|
366
405
|
gitbash --init [--prefix=PREFIX]
|
|
367
406
|
gitbash --config | --config-local | --config-user
|
|
368
407
|
|
|
@@ -371,9 +410,11 @@ Commands:
|
|
|
371
410
|
cleanstash Delete stashes (multi-select)
|
|
372
411
|
cleanup Find and delete leftover local branches
|
|
373
412
|
commit Commit with optional push
|
|
374
|
-
commits List
|
|
413
|
+
commits List the branch's commits with revert option
|
|
375
414
|
create Create feature branch with Jira parsing
|
|
376
415
|
pr Open PR in browser
|
|
416
|
+
repo Open the repository in browser
|
|
417
|
+
reset-repo Reset the branch like a fresh clone
|
|
377
418
|
stale List stale remote branches
|
|
378
419
|
stash Create named stash
|
|
379
420
|
stashes Interactive stash menu
|
|
@@ -385,6 +426,7 @@ Commands:
|
|
|
385
426
|
Options:
|
|
386
427
|
-h, --help Show this help message
|
|
387
428
|
-v, --version Show version number
|
|
429
|
+
--update Update gitbash to the latest release
|
|
388
430
|
--init Print shell functions for calling commands directly
|
|
389
431
|
(--prefix=gb- creates gb-commit, gb-switch, ...)
|
|
390
432
|
--config Configure gitbash and save to ~/.gitbashrc
|
|
@@ -445,6 +487,9 @@ main() {
|
|
|
445
487
|
-v|--version)
|
|
446
488
|
echo "gitbash $VERSION"
|
|
447
489
|
;;
|
|
490
|
+
--update)
|
|
491
|
+
gb_self_update
|
|
492
|
+
;;
|
|
448
493
|
--init)
|
|
449
494
|
shift
|
|
450
495
|
show_init "$@"
|
|
@@ -472,6 +517,9 @@ main() {
|
|
|
472
517
|
return 1
|
|
473
518
|
fi
|
|
474
519
|
gb_load_config
|
|
520
|
+
gb_update_check "$cmd" "$@"
|
|
521
|
+
# Commands run gitbash again (e.g. pr runs commit): no update prompt in there
|
|
522
|
+
export GITBASH_NESTED=1
|
|
475
523
|
# shellcheck source=/dev/null
|
|
476
524
|
source "$COMMANDS_DIR/$cmd.sh"
|
|
477
525
|
"$cmd" "$@"
|
|
@@ -0,0 +1,319 @@
|
|
|
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
|
+
(cd -P "$root/gitbash" 2>/dev/null && pwd)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# How this copy of gitbash was installed: npm, pnpm, npx, package, script, git or unknown
|
|
25
|
+
gb_install_method() {
|
|
26
|
+
case "$SCRIPT_DIR" in
|
|
27
|
+
*/_npx/*)
|
|
28
|
+
echo "npx"
|
|
29
|
+
return 0
|
|
30
|
+
;;
|
|
31
|
+
*/node_modules/gitbash)
|
|
32
|
+
local pm
|
|
33
|
+
for pm in npm pnpm; do
|
|
34
|
+
if command -v "$pm" >/dev/null 2>&1 &&
|
|
35
|
+
[[ "$(_gb_global_package_dir "$pm")" == "$SCRIPT_DIR" ]]; then
|
|
36
|
+
echo "$pm"
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
# A dependency of some project, or a package manager we don't know
|
|
41
|
+
echo "package"
|
|
42
|
+
return 0
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
if [[ -f "$SCRIPT_DIR/.gitbash-install" ]]; then
|
|
46
|
+
echo "script"
|
|
47
|
+
elif [[ -e "$SCRIPT_DIR/.git" ]]; then
|
|
48
|
+
echo "git"
|
|
49
|
+
elif [[ -f "$SCRIPT_DIR/package.json" && ! -e "$SCRIPT_DIR/README.md" ]]; then
|
|
50
|
+
# Installed by an install.sh that did not leave a marker yet
|
|
51
|
+
echo "script"
|
|
52
|
+
else
|
|
53
|
+
echo "unknown"
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# State (~/.local/state/gitbash/update-check)
|
|
59
|
+
# =============================================================================
|
|
60
|
+
|
|
61
|
+
_gb_update_state_file() {
|
|
62
|
+
echo "${XDG_STATE_HOME:-$HOME/.local/state}/gitbash/update-check"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Read the state into _GB_UC_CHECKED, _GB_UC_LATEST, _GB_UC_PROMPTED and _GB_UC_SKIPPED
|
|
66
|
+
_gb_update_state_load() {
|
|
67
|
+
local file key value
|
|
68
|
+
file=$(_gb_update_state_file)
|
|
69
|
+
_GB_UC_CHECKED=0 _GB_UC_LATEST="" _GB_UC_PROMPTED=0 _GB_UC_SKIPPED=""
|
|
70
|
+
[[ -f "$file" ]] || return 0
|
|
71
|
+
while IFS='=' read -r key value; do
|
|
72
|
+
case "$key" in
|
|
73
|
+
checked) [[ "$value" =~ ^[0-9]+$ ]] && _GB_UC_CHECKED="$value" ;;
|
|
74
|
+
latest) _GB_UC_LATEST="$value" ;;
|
|
75
|
+
prompted) [[ "$value" =~ ^[0-9]+$ ]] && _GB_UC_PROMPTED="$value" ;;
|
|
76
|
+
skipped) _GB_UC_SKIPPED="$value" ;;
|
|
77
|
+
esac
|
|
78
|
+
done < "$file"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Set one value, keeping the others (re-read first: a background check may have written)
|
|
82
|
+
# Usage: _gb_update_state_set checked|latest|prompted|skipped <value>
|
|
83
|
+
_gb_update_state_set() {
|
|
84
|
+
local file dir tmp
|
|
85
|
+
file=$(_gb_update_state_file)
|
|
86
|
+
dir=$(dirname "$file")
|
|
87
|
+
_gb_update_state_load
|
|
88
|
+
case "$1" in
|
|
89
|
+
checked) _GB_UC_CHECKED="$2" ;;
|
|
90
|
+
latest) _GB_UC_LATEST="$2" ;;
|
|
91
|
+
prompted) _GB_UC_PROMPTED="$2" ;;
|
|
92
|
+
skipped) _GB_UC_SKIPPED="$2" ;;
|
|
93
|
+
esac
|
|
94
|
+
mkdir -p "$dir" 2>/dev/null || return 1
|
|
95
|
+
tmp=$(mktemp "$dir/.update-check.XXXXXX" 2>/dev/null) || return 1
|
|
96
|
+
if ! printf 'checked=%s\nlatest=%s\nprompted=%s\nskipped=%s\n' \
|
|
97
|
+
"$_GB_UC_CHECKED" "$_GB_UC_LATEST" "$_GB_UC_PROMPTED" "$_GB_UC_SKIPPED" > "$tmp" ||
|
|
98
|
+
! mv -f "$tmp" "$file"; then
|
|
99
|
+
rm -f "$tmp"
|
|
100
|
+
return 1
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_gb_now() {
|
|
105
|
+
if [[ -n "${EPOCHSECONDS:-}" ]]; then
|
|
106
|
+
echo "$EPOCHSECONDS"
|
|
107
|
+
else
|
|
108
|
+
date +%s
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# =============================================================================
|
|
113
|
+
# Versions
|
|
114
|
+
# =============================================================================
|
|
115
|
+
|
|
116
|
+
# Print the contents of a URL
|
|
117
|
+
_gb_fetch() {
|
|
118
|
+
if command -v curl >/dev/null 2>&1; then
|
|
119
|
+
curl -fsSL --max-time 10 "$1"
|
|
120
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
121
|
+
wget -qO- -T 10 "$1"
|
|
122
|
+
else
|
|
123
|
+
return 1
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Print the version of the latest GitHub release, e.g. 2.1.0
|
|
128
|
+
gb_latest_version() {
|
|
129
|
+
local json tag
|
|
130
|
+
json=$(_gb_fetch "https://api.github.com/repos/$GB_REPO/releases/latest" 2>/dev/null) || return 1
|
|
131
|
+
tag=$(printf '%s\n' "$json" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
|
|
132
|
+
tag="${tag#v}"
|
|
133
|
+
[[ "$tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
|
134
|
+
echo "$tag"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Returns 0 if version $1 is newer than the running one
|
|
138
|
+
_gb_is_newer() {
|
|
139
|
+
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && ! _gb_version_ge "$VERSION" "$1"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# =============================================================================
|
|
143
|
+
# Update
|
|
144
|
+
# =============================================================================
|
|
145
|
+
|
|
146
|
+
# Install version $1 the way this copy was installed
|
|
147
|
+
gb_self_update_to() {
|
|
148
|
+
local latest="$1" method
|
|
149
|
+
method=$(gb_install_method)
|
|
150
|
+
case "$method" in
|
|
151
|
+
npm|pnpm)
|
|
152
|
+
local -a cmd=(npm install -g "gitbash@$latest")
|
|
153
|
+
[[ "$method" == "pnpm" ]] && cmd=(pnpm add -g "gitbash@$latest")
|
|
154
|
+
print_info "Running: ${cmd[*]}"
|
|
155
|
+
if ! "${cmd[@]}"; then
|
|
156
|
+
print_error "Update failed. Run '${cmd[*]}' yourself (it may need more permissions)."
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
;;
|
|
160
|
+
script)
|
|
161
|
+
_gb_self_update_script "$latest" || return 1
|
|
162
|
+
;;
|
|
163
|
+
npx)
|
|
164
|
+
print_info "gitbash runs through npx: use 'npx gitbash@latest' to get the newest version."
|
|
165
|
+
return 1
|
|
166
|
+
;;
|
|
167
|
+
git)
|
|
168
|
+
print_info "gitbash runs from a git checkout ($SCRIPT_DIR): update it with 'git pull'."
|
|
169
|
+
return 1
|
|
170
|
+
;;
|
|
171
|
+
package)
|
|
172
|
+
print_info "gitbash is a dependency in $SCRIPT_DIR: update it with your package manager."
|
|
173
|
+
return 1
|
|
174
|
+
;;
|
|
175
|
+
*)
|
|
176
|
+
print_error "Could not detect how gitbash was installed ($SCRIPT_DIR)."
|
|
177
|
+
echo "Reinstall it with 'npm i -g gitbash' or the install script (see the README)." >&2
|
|
178
|
+
return 1
|
|
179
|
+
;;
|
|
180
|
+
esac
|
|
181
|
+
|
|
182
|
+
# GITBASH_BIN, not SCRIPT_DIR: pnpm installs the new version in a new folder
|
|
183
|
+
local installed pkg_dir="${GITBASH_BIN%/bin/gitbash}"
|
|
184
|
+
installed=$(grep -o '"version": *"[^"]*"' "$pkg_dir/package.json" 2>/dev/null | head -1 | cut -d'"' -f4)
|
|
185
|
+
if [[ "$installed" != "$latest" ]]; then
|
|
186
|
+
print_error "Update finished, but gitbash in $pkg_dir is still at ${installed:-an unknown version}."
|
|
187
|
+
return 1
|
|
188
|
+
fi
|
|
189
|
+
print_success "Updated gitbash $VERSION → $latest"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Re-run the install script of the new release for the same locations
|
|
193
|
+
_gb_self_update_script() {
|
|
194
|
+
local latest="$1" bin_dir="" key value tmp
|
|
195
|
+
if [[ -f "$SCRIPT_DIR/.gitbash-install" ]]; then
|
|
196
|
+
while IFS='=' read -r key value; do
|
|
197
|
+
[[ "$key" == "bin_dir" ]] && bin_dir="$value"
|
|
198
|
+
done < "$SCRIPT_DIR/.gitbash-install"
|
|
199
|
+
fi
|
|
200
|
+
bin_dir="${bin_dir:-$HOME/.local/bin}"
|
|
201
|
+
|
|
202
|
+
local -a vars=(GITBASH_VERSION="$latest" GITBASH_INSTALL_DIR="$SCRIPT_DIR" GITBASH_BIN_DIR="$bin_dir")
|
|
203
|
+
local hint="curl -fsSL https://raw.githubusercontent.com/$GB_REPO/main/install.sh | sudo ${vars[*]} sh"
|
|
204
|
+
if [[ ! -w "$(dirname "$SCRIPT_DIR")" ]]; then
|
|
205
|
+
print_error "No permission to write to $(dirname "$SCRIPT_DIR"). Update with:"
|
|
206
|
+
echo " $hint" >&2
|
|
207
|
+
return 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
tmp=$(mktemp 2>/dev/null || mktemp -t gitbash) || return 1
|
|
211
|
+
if ! _gb_fetch "https://raw.githubusercontent.com/$GB_REPO/v$latest/install.sh" > "$tmp" 2>/dev/null ||
|
|
212
|
+
[[ ! -s "$tmp" ]]; then
|
|
213
|
+
rm -f "$tmp"
|
|
214
|
+
print_error "Could not download the install script of gitbash $latest."
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
print_info "Running the install script of gitbash $latest"
|
|
218
|
+
if ! env "${vars[@]}" sh "$tmp"; then
|
|
219
|
+
rm -f "$tmp"
|
|
220
|
+
print_error "Update failed."
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
rm -f "$tmp"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# gitbash --update
|
|
227
|
+
gb_self_update() {
|
|
228
|
+
local latest
|
|
229
|
+
print_info "Checking for updates..."
|
|
230
|
+
if ! latest=$(gb_latest_version); then
|
|
231
|
+
print_error "Could not check for updates (no network, or GitHub is not reachable)."
|
|
232
|
+
return 1
|
|
233
|
+
fi
|
|
234
|
+
_gb_update_state_set checked "$(_gb_now)"
|
|
235
|
+
_gb_update_state_set latest "$latest"
|
|
236
|
+
if ! _gb_is_newer "$latest"; then
|
|
237
|
+
print_success "gitbash $VERSION is up to date."
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
gb_self_update_to "$latest"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# =============================================================================
|
|
244
|
+
# Background check
|
|
245
|
+
# =============================================================================
|
|
246
|
+
|
|
247
|
+
# Returns 0 if update checks are on: not turned off, not in CI, not a git checkout
|
|
248
|
+
gb_update_checks_enabled() {
|
|
249
|
+
case "$_GB_ENV_NO_UPDATE_CHECKS" in
|
|
250
|
+
"") [[ "${GITBASH_NO_UPDATE_CHECKS:-no}" == "yes" ]] && return 1 ;;
|
|
251
|
+
no|0|false) ;;
|
|
252
|
+
*) return 1 ;;
|
|
253
|
+
esac
|
|
254
|
+
local var
|
|
255
|
+
for var in CI CONTINUOUS_INTEGRATION BUILD_NUMBER GITHUB_ACTIONS GITLAB_CI TF_BUILD \
|
|
256
|
+
JENKINS_URL BUILDKITE CIRCLECI TRAVIS TEAMCITY_VERSION BITBUCKET_BUILD_NUMBER; do
|
|
257
|
+
case "${!var:-}" in
|
|
258
|
+
""|false|0) ;;
|
|
259
|
+
*) return 1 ;;
|
|
260
|
+
esac
|
|
261
|
+
done
|
|
262
|
+
[[ ! -e "$SCRIPT_DIR/.git" ]]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Run before a command: start a background check when one is due, and offer a
|
|
266
|
+
# known new version (at most every other day, never again for a skipped one).
|
|
267
|
+
# On a successful update, runs the command with the new version instead.
|
|
268
|
+
# Usage: gb_update_check <command> [args...]
|
|
269
|
+
gb_update_check() {
|
|
270
|
+
# Not when gitbash runs itself from a command (the parent already checked)
|
|
271
|
+
[[ -z "${GITBASH_NESTED:-}" ]] || return 0
|
|
272
|
+
gb_update_checks_enabled || return 0
|
|
273
|
+
|
|
274
|
+
local now
|
|
275
|
+
now=$(_gb_now)
|
|
276
|
+
_gb_update_state_load
|
|
277
|
+
if (( now - _GB_UC_CHECKED >= GB_UPDATE_CHECK_INTERVAL || _GB_UC_CHECKED > now )); then
|
|
278
|
+
_gb_update_state_set checked "$now"
|
|
279
|
+
(
|
|
280
|
+
local latest
|
|
281
|
+
latest=$(gb_latest_version) && _gb_update_state_set latest "$latest"
|
|
282
|
+
) < /dev/null > /dev/null 2>&1 &
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
local latest="$_GB_UC_LATEST"
|
|
286
|
+
_gb_is_newer "$latest" || return 0
|
|
287
|
+
[[ "$latest" != "$_GB_UC_SKIPPED" ]] || return 0
|
|
288
|
+
(( now - _GB_UC_PROMPTED >= GB_UPDATE_PROMPT_INTERVAL || _GB_UC_PROMPTED > now )) || return 0
|
|
289
|
+
# Only ask people at a terminal, never scripts
|
|
290
|
+
[[ -t 0 && -t 1 && -t 2 && "${GITBASH_ASSUME_YES:-}" != "1" ]] || return 0
|
|
291
|
+
local arg
|
|
292
|
+
for arg in "${@:2}"; do
|
|
293
|
+
[[ "$arg" == "-y" || "$arg" == "--yes" ]] && return 0
|
|
294
|
+
done
|
|
295
|
+
|
|
296
|
+
_gb_update_state_set prompted "$now"
|
|
297
|
+
print_info "gitbash $latest is available (you have $VERSION)."
|
|
298
|
+
local choice
|
|
299
|
+
gb_choice choice "Update now? [y]es, [n]ot now, [s]kip this version (y/N/s):" "yns" "n"
|
|
300
|
+
case "$choice" in
|
|
301
|
+
y)
|
|
302
|
+
if gb_self_update_to "$latest"; then
|
|
303
|
+
echo
|
|
304
|
+
exec "$GITBASH_BIN" "$@"
|
|
305
|
+
fi
|
|
306
|
+
echo
|
|
307
|
+
;;
|
|
308
|
+
s)
|
|
309
|
+
_gb_update_state_set skipped "$latest"
|
|
310
|
+
print_info "You won't be asked about $latest again. Run 'gitbash --update' to update any time."
|
|
311
|
+
echo
|
|
312
|
+
;;
|
|
313
|
+
*)
|
|
314
|
+
print_info "Run 'gitbash --update' to update any time."
|
|
315
|
+
echo
|
|
316
|
+
;;
|
|
317
|
+
esac
|
|
318
|
+
return 0
|
|
319
|
+
}
|