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
package/commands/commits.sh
CHANGED
|
@@ -5,21 +5,28 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
5
5
|
# shellcheck source=_utils.sh
|
|
6
6
|
source "$SOURCE_DIR/_utils.sh"
|
|
7
7
|
|
|
8
|
-
# List
|
|
8
|
+
# List the current branch's own commits and optionally revert selected ones
|
|
9
9
|
commits() {
|
|
10
10
|
if [[ "${1:-}" == "-v" || "${1:-}" == "--version" ]]; then
|
|
11
11
|
echo "gitbash ${FUNCNAME[0]} v$VERSION"
|
|
12
12
|
return 0
|
|
13
13
|
fi
|
|
14
14
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
15
|
-
|
|
16
|
-
Usage: commits [COUNT]
|
|
15
|
+
gb_help << 'EOF'
|
|
16
|
+
Usage: commits [-a|--all] [COUNT]
|
|
17
17
|
|
|
18
|
-
List
|
|
18
|
+
List the commits made on the current branch with option to revert selected ones.
|
|
19
|
+
|
|
20
|
+
On a feature branch only the commits that are not in the base branch are
|
|
21
|
+
shown. On the base branch its own history is shown (--first-parent), so
|
|
22
|
+
merged branches appear as their merge commit.
|
|
19
23
|
|
|
20
24
|
Arguments:
|
|
21
25
|
COUNT Number of commits to show (default: 20)
|
|
22
26
|
|
|
27
|
+
Options:
|
|
28
|
+
-a, --all Show all recent commits reachable from HEAD
|
|
29
|
+
|
|
23
30
|
Navigation:
|
|
24
31
|
↑/↓ Navigate through commits
|
|
25
32
|
TAB Select/deselect commit for revert
|
|
@@ -27,6 +34,7 @@ Navigation:
|
|
|
27
34
|
ESC/Ctrl-C Exit without action
|
|
28
35
|
|
|
29
36
|
Notes:
|
|
37
|
+
- If the upstream has new commits, offers to fast-forward first
|
|
30
38
|
- Preview shows the full commit diff
|
|
31
39
|
- Selected commits are reverted newest first, whatever order you picked them in
|
|
32
40
|
- Merge commits are reverted against their first parent (the branch they were merged into)
|
|
@@ -35,6 +43,7 @@ Notes:
|
|
|
35
43
|
Examples:
|
|
36
44
|
$ commits
|
|
37
45
|
$ commits 50
|
|
46
|
+
$ commits --all
|
|
38
47
|
|
|
39
48
|
Requirements:
|
|
40
49
|
- Must be in a git repository
|
|
@@ -46,16 +55,56 @@ EOF
|
|
|
46
55
|
|
|
47
56
|
require_git_repo || return 1
|
|
48
57
|
|
|
49
|
-
local count="
|
|
58
|
+
local all=false count=""
|
|
59
|
+
while [[ $# -gt 0 ]]; do
|
|
60
|
+
case "$1" in
|
|
61
|
+
-a|--all) all=true ;;
|
|
62
|
+
-*) print_error "Unknown option '$1'. See 'commits --help'."; return 1 ;;
|
|
63
|
+
*)
|
|
64
|
+
if [[ -n "$count" ]]; then
|
|
65
|
+
print_error "Unexpected argument '$1'. See 'commits --help'."
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
count="$1"
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
shift
|
|
72
|
+
done
|
|
73
|
+
count="${count:-20}"
|
|
50
74
|
if ! [[ "$count" =~ ^[1-9][0-9]*$ ]]; then
|
|
51
75
|
print_error "COUNT must be a positive number (got '$count')."
|
|
52
76
|
return 1
|
|
53
77
|
fi
|
|
54
78
|
require_fzf || return 1
|
|
55
79
|
|
|
80
|
+
# Offer to pull remote commits first, so they show up (and can be reverted)
|
|
81
|
+
gb_offer_fast_forward || true
|
|
82
|
+
|
|
56
83
|
local current_branch
|
|
57
84
|
current_branch=$(gb_current_branch) || current_branch="(detached HEAD)"
|
|
58
85
|
|
|
86
|
+
# -----------------------------
|
|
87
|
+
# Which commits: the branch's own (not in the base branch) or, on the
|
|
88
|
+
# base branch, its first-parent history
|
|
89
|
+
# -----------------------------
|
|
90
|
+
local log_args=() scope="last $count commits" empty_hint="" base_branch base_ref
|
|
91
|
+
if [[ "$all" == false ]] && base_branch=$(gb_base_branch 2>/dev/null); then
|
|
92
|
+
base_ref=$(gb_base_ref "$base_branch")
|
|
93
|
+
if [[ "$current_branch" == "$base_branch" ]]; then
|
|
94
|
+
log_args=(--first-parent)
|
|
95
|
+
scope="last $count commits on '$base_branch' (merged branches as merge commits)"
|
|
96
|
+
elif git rev-parse --verify --quiet "$base_ref^{commit}" >/dev/null; then
|
|
97
|
+
log_args=(--not "$base_ref")
|
|
98
|
+
# Also exclude the local base branch when it is ahead of the remote one
|
|
99
|
+
if [[ "$base_ref" != "$base_branch" ]] &&
|
|
100
|
+
git show-ref --verify --quiet "refs/heads/$base_branch"; then
|
|
101
|
+
log_args+=("$base_branch")
|
|
102
|
+
fi
|
|
103
|
+
scope="commits not in '$base_ref' (up to $count)"
|
|
104
|
+
empty_hint="No commits on '$current_branch' that are not in '$base_ref'. Use 'commits --all' to show all recent commits."
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
59
108
|
# -----------------------------
|
|
60
109
|
# Build commit list (fields separated by \x1f, subject last)
|
|
61
110
|
# -----------------------------
|
|
@@ -67,10 +116,10 @@ EOF
|
|
|
67
116
|
fi
|
|
68
117
|
commit_list+=$(printf "%-10s %-60s %-15s %s" "$hash" "$subject" "$date" "$author")
|
|
69
118
|
commit_list+=$'\t'"$hash"$'\n'
|
|
70
|
-
done < <(git log -n "$count" --format='%h%x1f%cr%x1f%an%x1f%s' 2>/dev/null)
|
|
119
|
+
done < <(git log -n "$count" --format='%h%x1f%cr%x1f%an%x1f%s' HEAD ${log_args[@]+"${log_args[@]}"} 2>/dev/null)
|
|
71
120
|
|
|
72
121
|
if [[ -z "$commit_list" ]]; then
|
|
73
|
-
echo "No commits found."
|
|
122
|
+
echo "${empty_hint:-No commits found.}"
|
|
74
123
|
return 0
|
|
75
124
|
fi
|
|
76
125
|
|
|
@@ -78,11 +127,11 @@ EOF
|
|
|
78
127
|
pager=$(gb_diff_pager)
|
|
79
128
|
preview='
|
|
80
129
|
hash=$(printf "%s" {} | cut -f2)
|
|
81
|
-
git show --stat --color
|
|
130
|
+
git show --stat --color=$GB_COLOR "$hash" 2>/dev/null
|
|
82
131
|
echo
|
|
83
132
|
echo "─────────────────────────────────────────────────────"
|
|
84
133
|
echo
|
|
85
|
-
git show --color
|
|
134
|
+
git show --color=$GB_COLOR --format= "$hash" 2>/dev/null | head -300 | __PAGER__'
|
|
86
135
|
preview="${preview//__PAGER__/$pager}"
|
|
87
136
|
|
|
88
137
|
local selection
|
|
@@ -92,7 +141,7 @@ EOF
|
|
|
92
141
|
--reverse \
|
|
93
142
|
--border \
|
|
94
143
|
--header="[TAB] select for revert | [Enter] revert selected | [ESC] exit
|
|
95
|
-
Showing
|
|
144
|
+
Showing $scope" \
|
|
96
145
|
--multi \
|
|
97
146
|
--delimiter=$'\t' \
|
|
98
147
|
--with-nth=1 \
|
package/commands/create.sh
CHANGED
package/commands/pr.sh
CHANGED
|
@@ -5,52 +5,6 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
5
5
|
# shellcheck source=_utils.sh
|
|
6
6
|
source "$SOURCE_DIR/_utils.sh"
|
|
7
7
|
|
|
8
|
-
# Convert a remote URL to the repository's web URL (credentials and .git removed).
|
|
9
|
-
# Handles https://[user[:token]@]host/path, ssh://[user@]host[:port]/path and user@host:path.
|
|
10
|
-
_pr_web_base() {
|
|
11
|
-
local url="$1" scheme="https" rest host path
|
|
12
|
-
case "$url" in
|
|
13
|
-
http://*|https://*)
|
|
14
|
-
scheme="${url%%://*}"
|
|
15
|
-
rest="${url#*://}"
|
|
16
|
-
host="${rest%%/*}"
|
|
17
|
-
host="${host##*@}"
|
|
18
|
-
path="${rest#*/}"
|
|
19
|
-
;;
|
|
20
|
-
ssh://*)
|
|
21
|
-
rest="${url#ssh://}"
|
|
22
|
-
host="${rest%%/*}"
|
|
23
|
-
host="${host##*@}"
|
|
24
|
-
host="${host%%:*}"
|
|
25
|
-
path="${rest#*/}"
|
|
26
|
-
;;
|
|
27
|
-
*@*:*)
|
|
28
|
-
host="${url%%:*}"
|
|
29
|
-
host="${host##*@}"
|
|
30
|
-
path="${url#*:}"
|
|
31
|
-
;;
|
|
32
|
-
*)
|
|
33
|
-
return 1
|
|
34
|
-
;;
|
|
35
|
-
esac
|
|
36
|
-
path="${path%/}"
|
|
37
|
-
path="${path%.git}"
|
|
38
|
-
if [[ -z "$host" || -z "$path" || ( -n "$rest" && "$path" == "$rest" ) ]]; then
|
|
39
|
-
return 1
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Azure DevOps SSH: ssh.dev.azure.com:v3/org/project/repo
|
|
43
|
-
if [[ "$host" == "ssh.dev.azure.com" && "$path" == v3/*/*/* ]]; then
|
|
44
|
-
local org project repo
|
|
45
|
-
path="${path#v3/}"
|
|
46
|
-
org="${path%%/*}"; path="${path#*/}"
|
|
47
|
-
project="${path%%/*}"; repo="${path#*/}"
|
|
48
|
-
echo "https://dev.azure.com/$org/$project/_git/$repo"
|
|
49
|
-
return 0
|
|
50
|
-
fi
|
|
51
|
-
echo "$scheme://$host/$path"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
8
|
# Web URL for creating a pull/merge request for a branch
|
|
55
9
|
_pr_create_url() {
|
|
56
10
|
local base="$1" branch="$2" encoded
|
|
@@ -83,7 +37,7 @@ pr() {
|
|
|
83
37
|
return 0
|
|
84
38
|
;;
|
|
85
39
|
-h|--help)
|
|
86
|
-
|
|
40
|
+
gb_help << 'EOF'
|
|
87
41
|
Usage: pr [OPTIONS]
|
|
88
42
|
|
|
89
43
|
Open the current branch's pull request in your browser.
|
|
@@ -105,7 +59,7 @@ Behavior:
|
|
|
105
59
|
Bitbucket <repo>/pull-requests/new?source=<branch>
|
|
106
60
|
Azure DevOps <repo>/pullrequestcreate?sourceRef=<branch>
|
|
107
61
|
- Works with SSH and HTTPS remotes; credentials in the remote URL are never used
|
|
108
|
-
- Opens the browser
|
|
62
|
+
- Opens the browser (macOS, Linux, WSL, Git Bash on Windows), otherwise prints the URL
|
|
109
63
|
|
|
110
64
|
Examples:
|
|
111
65
|
$ pr
|
|
@@ -147,7 +101,7 @@ EOF
|
|
|
147
101
|
print_error "Detached HEAD: check out a branch first."
|
|
148
102
|
return 1
|
|
149
103
|
fi
|
|
150
|
-
if ! base=$(
|
|
104
|
+
if ! base=$(gb_web_url "$remote_url"); then
|
|
151
105
|
print_error "Don't know how to open '$remote_url' in a browser."
|
|
152
106
|
return 1
|
|
153
107
|
fi
|
package/commands/repo.sh
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Source common utilities
|
|
4
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
# shellcheck source=_utils.sh
|
|
6
|
+
source "$SOURCE_DIR/_utils.sh"
|
|
7
|
+
|
|
8
|
+
# Open the repository's web page
|
|
9
|
+
repo() {
|
|
10
|
+
local print_only=false
|
|
11
|
+
|
|
12
|
+
while [[ $# -gt 0 ]]; do
|
|
13
|
+
case "$1" in
|
|
14
|
+
-v|--version)
|
|
15
|
+
echo "gitbash ${FUNCNAME[0]} v$VERSION"
|
|
16
|
+
return 0
|
|
17
|
+
;;
|
|
18
|
+
-h|--help)
|
|
19
|
+
gb_help << 'EOF_HELP'
|
|
20
|
+
Usage: repo [OPTIONS]
|
|
21
|
+
|
|
22
|
+
Open the repository's web page in your browser.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--print Print the URL instead of opening it
|
|
26
|
+
-h, --help Show this help message
|
|
27
|
+
|
|
28
|
+
Behavior:
|
|
29
|
+
- Uses the URL of the configured remote (GITBASH_REMOTE, default: origin)
|
|
30
|
+
- With the GitHub CLI (gh) installed and logged in, opens it with 'gh repo view --web'
|
|
31
|
+
- Otherwise opens the URL derived from the remote
|
|
32
|
+
(GitHub, GitHub Enterprise, GitLab, Bitbucket, Azure DevOps)
|
|
33
|
+
- Works with SSH and HTTPS remotes; credentials in the remote URL are never used
|
|
34
|
+
- Opens the browser (macOS, Linux, WSL, Git Bash on Windows), otherwise prints the URL
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
$ repo
|
|
38
|
+
$ repo --print
|
|
39
|
+
|
|
40
|
+
EOF_HELP
|
|
41
|
+
return 0
|
|
42
|
+
;;
|
|
43
|
+
--print)
|
|
44
|
+
print_only=true
|
|
45
|
+
shift
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
print_error "Unknown argument: $1"
|
|
49
|
+
echo "Usage: repo [--print]" >&2
|
|
50
|
+
return 1
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
require_git_repo || return 1
|
|
56
|
+
|
|
57
|
+
local remote remote_url url
|
|
58
|
+
remote=$(gb_remote)
|
|
59
|
+
if ! remote_url=$(git config --get "remote.$remote.url"); then
|
|
60
|
+
print_error "No remote '$remote' found."
|
|
61
|
+
return 1
|
|
62
|
+
fi
|
|
63
|
+
if ! url=$(gb_web_url "$remote_url"); then
|
|
64
|
+
print_error "Don't know how to open '$remote_url' in a browser."
|
|
65
|
+
return 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [[ "$print_only" == true ]]; then
|
|
69
|
+
echo "$url"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [[ "$url" != *gitlab* && "$url" != *bitbucket.org/* && "$url" != *dev.azure.com/* && "$url" != *visualstudio.com/* ]] &&
|
|
74
|
+
command -v gh >/dev/null 2>&1; then
|
|
75
|
+
if GH_PROMPT_DISABLED=1 gh repo view "$url" --web >/dev/null 2>&1; then
|
|
76
|
+
print_success "Opened $url"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
gb_open_url "$url"
|
|
82
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Source common utilities
|
|
4
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
# shellcheck source=_utils.sh
|
|
6
|
+
source "$SOURCE_DIR/_utils.sh"
|
|
7
|
+
|
|
8
|
+
# Abort an unfinished merge, rebase, cherry-pick, revert or am
|
|
9
|
+
_reset_repo_abort_operations() {
|
|
10
|
+
local path
|
|
11
|
+
if [[ -d "$(git rev-parse --git-path rebase-merge)" || -d "$(git rev-parse --git-path rebase-apply)" ]]; then
|
|
12
|
+
path=$(git rev-parse --git-path rebase-apply)
|
|
13
|
+
if [[ -f "$path/applying" ]]; then
|
|
14
|
+
git am --abort >/dev/null 2>&1
|
|
15
|
+
else
|
|
16
|
+
git rebase --abort >/dev/null 2>&1
|
|
17
|
+
fi
|
|
18
|
+
fi
|
|
19
|
+
[[ -f "$(git rev-parse --git-path MERGE_HEAD)" ]] && git merge --abort >/dev/null 2>&1
|
|
20
|
+
[[ -f "$(git rev-parse --git-path CHERRY_PICK_HEAD)" ]] && git cherry-pick --abort >/dev/null 2>&1
|
|
21
|
+
[[ -f "$(git rev-parse --git-path REVERT_HEAD)" ]] && git revert --abort >/dev/null 2>&1
|
|
22
|
+
return 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Reset the current branch to its state on the remote and delete all
|
|
26
|
+
# untracked and ignored files, like a fresh clone
|
|
27
|
+
reset-repo() {
|
|
28
|
+
local dry_run=false
|
|
29
|
+
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
-v|--version)
|
|
33
|
+
echo "gitbash ${FUNCNAME[0]} v$VERSION"
|
|
34
|
+
return 0
|
|
35
|
+
;;
|
|
36
|
+
-h|--help)
|
|
37
|
+
gb_help << 'EOF_HELP'
|
|
38
|
+
Usage: reset-repo [OPTIONS]
|
|
39
|
+
|
|
40
|
+
Reset the current branch like a fresh clone: discard all local changes and
|
|
41
|
+
unpushed commits, and delete untracked and ignored files (build output,
|
|
42
|
+
node_modules, ...).
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
-n, --dry-run Only show what would be reset and deleted
|
|
46
|
+
-y, --yes Don't ask for confirmation
|
|
47
|
+
-h, --help Show this help message
|
|
48
|
+
|
|
49
|
+
Behavior:
|
|
50
|
+
- Fetches the configured remote (GITBASH_REMOTE, default: origin) and resets
|
|
51
|
+
the branch to <remote>/<branch>; a branch that is not on the remote keeps
|
|
52
|
+
its commits and only loses the local changes
|
|
53
|
+
- Aborts an unfinished merge, rebase, cherry-pick or revert first
|
|
54
|
+
- Keeps files matching GITBASH_RESET_KEEP (space-separated .gitignore
|
|
55
|
+
patterns, e.g. '.env .idea/'), .gitbashrc-user and nested repositories
|
|
56
|
+
- Stashes and other branches are not touched
|
|
57
|
+
- Commits that are lost can be recovered with 'git reflog' for a while
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
$ reset-repo --dry-run
|
|
61
|
+
$ reset-repo
|
|
62
|
+
|
|
63
|
+
EOF_HELP
|
|
64
|
+
return 0
|
|
65
|
+
;;
|
|
66
|
+
-n|--dry-run)
|
|
67
|
+
dry_run=true
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
70
|
+
-y|--yes)
|
|
71
|
+
export GITBASH_ASSUME_YES=1
|
|
72
|
+
shift
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
print_error "Unknown argument: $1"
|
|
76
|
+
echo "Usage: reset-repo [--dry-run] [--yes]" >&2
|
|
77
|
+
return 1
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
require_git_repo || return 1
|
|
83
|
+
|
|
84
|
+
local repo_root
|
|
85
|
+
repo_root=$(git rev-parse --show-toplevel) || return 1
|
|
86
|
+
cd "$repo_root" || return 1
|
|
87
|
+
|
|
88
|
+
# During a rebase HEAD is detached; the branch is the one being rebased
|
|
89
|
+
local branch="" head_name
|
|
90
|
+
for head_name in rebase-merge/head-name rebase-apply/head-name; do
|
|
91
|
+
head_name=$(git rev-parse --git-path "$head_name")
|
|
92
|
+
if [[ -f "$head_name" ]]; then
|
|
93
|
+
branch=$(cat "$head_name")
|
|
94
|
+
branch="${branch#refs/heads/}"
|
|
95
|
+
break
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
if [[ -z "$branch" ]] && ! branch=$(gb_current_branch); then
|
|
99
|
+
print_error "Detached HEAD: check out a branch first."
|
|
100
|
+
return 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
local remote target
|
|
104
|
+
remote=$(gb_remote)
|
|
105
|
+
if ! git remote get-url "$remote" >/dev/null 2>&1; then
|
|
106
|
+
print_error "No remote '$remote' configured."
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
print_info "Fetching '$remote'..."
|
|
110
|
+
if ! git fetch --quiet --prune "$remote"; then
|
|
111
|
+
print_error "Failed to fetch '$remote'."
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
if git show-ref --verify --quiet "refs/remotes/$remote/$branch"; then
|
|
115
|
+
target="$remote/$branch"
|
|
116
|
+
else
|
|
117
|
+
target="$branch"
|
|
118
|
+
print_warning "'$branch' is not on '$remote': keeping its commits, only local changes are discarded."
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Patterns of files to keep (git clean -e); .gitbashrc-user is always kept
|
|
122
|
+
local -a keep clean_args
|
|
123
|
+
local pattern
|
|
124
|
+
IFS=' ' read -r -a keep <<< "${GITBASH_RESET_KEEP:-}"
|
|
125
|
+
keep+=(".gitbashrc-user")
|
|
126
|
+
clean_args=(-d -x)
|
|
127
|
+
for pattern in "${keep[@]}"; do
|
|
128
|
+
[[ -n "$pattern" ]] && clean_args+=(-e "$pattern")
|
|
129
|
+
done
|
|
130
|
+
|
|
131
|
+
# What would be lost
|
|
132
|
+
local lost_commits changes untracked
|
|
133
|
+
lost_commits=$(git log --oneline "$target..refs/heads/$branch")
|
|
134
|
+
changes=$(git status --porcelain --untracked-files=no)
|
|
135
|
+
untracked=$(git clean -n "${clean_args[@]}")
|
|
136
|
+
|
|
137
|
+
local head_commit
|
|
138
|
+
head_commit=$(git rev-parse "refs/heads/$branch")
|
|
139
|
+
if [[ -z "$lost_commits" && -z "$changes" && -z "$untracked" &&
|
|
140
|
+
"$(git rev-parse HEAD)" == "$(git rev-parse "$target")" ]]; then
|
|
141
|
+
_reset_repo_abort_operations
|
|
142
|
+
print_success "'$branch' is already like a fresh clone of '$target'."
|
|
143
|
+
return 0
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
echo "Resetting '$branch' to '$target':"
|
|
147
|
+
if [[ -n "$lost_commits" ]]; then
|
|
148
|
+
echo
|
|
149
|
+
print_warning "$(printf '%s\n' "$lost_commits" | wc -l | tr -d ' ') unpushed commit(s) will be removed from '$branch':"
|
|
150
|
+
printf '%s\n' "$lost_commits" | sed 's/^/ /'
|
|
151
|
+
fi
|
|
152
|
+
if [[ -n "$changes" ]]; then
|
|
153
|
+
echo
|
|
154
|
+
echo "Local changes to discard:"
|
|
155
|
+
printf '%s\n' "$changes" | sed 's/^/ /'
|
|
156
|
+
fi
|
|
157
|
+
if [[ -n "$untracked" ]]; then
|
|
158
|
+
echo
|
|
159
|
+
echo "Untracked and ignored files to delete:"
|
|
160
|
+
printf '%s\n' "$untracked" | sed 's/^Would remove / /'
|
|
161
|
+
fi
|
|
162
|
+
if [[ ${#keep[@]} -gt 1 ]]; then
|
|
163
|
+
echo
|
|
164
|
+
echo "Kept (GITBASH_RESET_KEEP): ${GITBASH_RESET_KEEP}"
|
|
165
|
+
fi
|
|
166
|
+
echo
|
|
167
|
+
|
|
168
|
+
if [[ "$dry_run" == true ]]; then
|
|
169
|
+
print_info "Dry run - nothing was changed."
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if ! gb_confirm "Reset '$branch' and delete these files?" n; then
|
|
174
|
+
print_info "Aborted - nothing was changed."
|
|
175
|
+
return 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
_reset_repo_abort_operations
|
|
179
|
+
if [[ "$(gb_current_branch)" != "$branch" ]] && ! git switch --quiet --force "$branch"; then
|
|
180
|
+
print_error "Failed to check out '$branch'."
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
if ! git reset --quiet --hard "$target"; then
|
|
184
|
+
print_error "Failed to reset '$branch' to '$target'."
|
|
185
|
+
return 1
|
|
186
|
+
fi
|
|
187
|
+
if ! git clean -q -f "${clean_args[@]}"; then
|
|
188
|
+
print_error "Failed to delete untracked files."
|
|
189
|
+
return 1
|
|
190
|
+
fi
|
|
191
|
+
if [[ -f .gitmodules ]]; then
|
|
192
|
+
git submodule update --init --recursive --force --quiet ||
|
|
193
|
+
print_warning "Failed to update submodules."
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
print_success "'$branch' is now like a fresh clone of '$target'."
|
|
197
|
+
if [[ -n "$lost_commits" ]]; then
|
|
198
|
+
print_info "Removed commits can be recovered with 'git reflog' (previous HEAD: ${head_commit:0:12})."
|
|
199
|
+
fi
|
|
200
|
+
}
|
package/commands/stale.sh
CHANGED
|
@@ -28,7 +28,7 @@ stale() {
|
|
|
28
28
|
return 0
|
|
29
29
|
;;
|
|
30
30
|
-h|--help)
|
|
31
|
-
|
|
31
|
+
gb_help << 'EOF'
|
|
32
32
|
Usage: stale [OPTIONS] [FILTER...]
|
|
33
33
|
|
|
34
34
|
Show remote branches ordered by last commit date (oldest first) and delete
|
|
@@ -229,7 +229,7 @@ TOGGLE_EOF
|
|
|
229
229
|
echo \"Branch: $remote_q/\$branch\"
|
|
230
230
|
echo
|
|
231
231
|
echo 'Recent commits:'
|
|
232
|
-
git log --oneline --color
|
|
232
|
+
git log --oneline --color=\$GB_COLOR -n 15 \"refs/remotes/$remote_q/\$branch\" 2>/dev/null || echo 'No commits found'
|
|
233
233
|
" \
|
|
234
234
|
--preview-window=right:35% \
|
|
235
235
|
< "$tmp_dir/$(cat "$tmp_dir/state")"
|
package/commands/stash.sh
CHANGED
package/commands/stashes.sh
CHANGED
|
@@ -14,7 +14,7 @@ stashes() {
|
|
|
14
14
|
return 0
|
|
15
15
|
fi
|
|
16
16
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
17
|
-
|
|
17
|
+
gb_help << 'EOF'
|
|
18
18
|
Usage: stashes [OPTIONS]
|
|
19
19
|
|
|
20
20
|
Interactive menu to manage git stashes.
|
|
@@ -87,7 +87,7 @@ Examples:
|
|
|
87
87
|
Actions:
|
|
88
88
|
💾 Stash - Create a new stash with all changes
|
|
89
89
|
📦 Unstash - Apply stash to working directory, optionally drop it
|
|
90
|
-
🗑️ Clean up
|
|
90
|
+
🗑️ Clean up - Delete stashes without applying (supports multi-select)
|
|
91
91
|
|
|
92
92
|
Requirements:
|
|
93
93
|
- fzf (fuzzy finder)
|
package/commands/status.sh
CHANGED
|
@@ -45,7 +45,7 @@ status() {
|
|
|
45
45
|
return 0
|
|
46
46
|
fi
|
|
47
47
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
48
|
-
|
|
48
|
+
gb_help << 'EOF'
|
|
49
49
|
Usage: status [OPTIONS]
|
|
50
50
|
|
|
51
51
|
Interactive git status: stage, unstage, discard and commit with diff previews.
|
|
@@ -92,8 +92,8 @@ EOF
|
|
|
92
92
|
echo "=== UNTRACKED ==="; echo
|
|
93
93
|
if [[ -d "$file" ]]; then ls -la "$file"; else __BAT__ "$file" 2>/dev/null || echo "Cannot preview file"; fi
|
|
94
94
|
else
|
|
95
|
-
staged=$(git diff --cached --color
|
|
96
|
-
unstaged=$(git diff --color
|
|
95
|
+
staged=$(git diff --cached --color=$GB_COLOR -- "$file" 2>/dev/null)
|
|
96
|
+
unstaged=$(git diff --color=$GB_COLOR -- "$file" 2>/dev/null)
|
|
97
97
|
if [[ -n "$staged" ]]; then echo "=== STAGED CHANGES ==="; echo; printf "%s\n" "$staged" | __PAGER__; fi
|
|
98
98
|
if [[ -n "$unstaged" ]]; then
|
|
99
99
|
[[ -n "$staged" ]] && echo
|
package/commands/switch.sh
CHANGED
|
@@ -100,14 +100,15 @@ switch() {
|
|
|
100
100
|
return 0
|
|
101
101
|
;;
|
|
102
102
|
-h|--help)
|
|
103
|
-
|
|
103
|
+
gb_help << 'EOF'
|
|
104
104
|
Usage: switch [FILTER...]
|
|
105
105
|
|
|
106
106
|
Select a git branch using fzf and switch to it.
|
|
107
107
|
|
|
108
108
|
Arguments:
|
|
109
109
|
FILTER... Optional search words to pre-fill fzf (joined with spaces).
|
|
110
|
-
|
|
110
|
+
Switches directly on an exact branch name, or if exactly
|
|
111
|
+
one branch name contains them.
|
|
111
112
|
|
|
112
113
|
Options:
|
|
113
114
|
-h, --help Show this help message
|
|
@@ -152,10 +153,19 @@ EOF
|
|
|
152
153
|
fi
|
|
153
154
|
|
|
154
155
|
# -----------------------------
|
|
155
|
-
#
|
|
156
|
+
# Exact or single match: switch directly
|
|
156
157
|
# -----------------------------
|
|
157
158
|
local selected="" query="$filter"
|
|
158
159
|
if [[ -n "$filter" ]]; then
|
|
160
|
+
# An exact branch name wins even if other branches contain it too
|
|
161
|
+
# (local first, then a remote branch like "origin/<filter>")
|
|
162
|
+
selected=$(printf '%s\n' "$branch_list" | awk -F'\t' -v f="$filter" '$2 == "local" && $3 == f { print; exit }')
|
|
163
|
+
if [[ -z "$selected" ]]; then
|
|
164
|
+
selected=$(printf '%s\n' "$branch_list" | awk -F'\t' -v f="$filter" '$2 == "remote" && ($3 == f || substr($3, index($3, "/") + 1) == f) { print; exit }')
|
|
165
|
+
fi
|
|
166
|
+
[[ -n "$selected" ]] && print_info "Exact match found, switching directly..."
|
|
167
|
+
fi
|
|
168
|
+
if [[ -n "$filter" && -z "$selected" ]]; then
|
|
159
169
|
local matches match_count
|
|
160
170
|
matches=$(printf '%s\n' "$branch_list" | awk -F'\t' '$2 != "spacer"' |
|
|
161
171
|
while IFS= read -r line; do
|
|
@@ -192,9 +202,9 @@ EOF
|
|
|
192
202
|
remote) ref=\"refs/remotes/\$name\" ;;
|
|
193
203
|
*) echo 'Spacer - not selectable'; exit 0 ;;
|
|
194
204
|
esac
|
|
195
|
-
git log --color
|
|
205
|
+
git log --color=\$GB_COLOR -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
206
|
echo
|
|
197
|
-
git log --oneline --color
|
|
207
|
+
git log --oneline --color=\$GB_COLOR -n 10 \"\$ref\" 2>/dev/null
|
|
198
208
|
" \
|
|
199
209
|
--preview-window=right:50% \
|
|
200
210
|
--header="[Enter] switch | [Del] delete local branch | [Esc] exit | Current: $current_branch" \
|
|
@@ -232,25 +242,6 @@ EOF
|
|
|
232
242
|
fi
|
|
233
243
|
print_success "Switched to branch: $(gb_current_branch)"
|
|
234
244
|
|
|
235
|
-
# -----------------------------
|
|
236
245
|
# 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
|
|
252
|
-
fi
|
|
253
|
-
fi
|
|
254
|
-
fi
|
|
255
|
-
return 0
|
|
246
|
+
gb_offer_fast_forward
|
|
256
247
|
}
|