gitbash 0.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/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/gitbash +124 -0
- package/commands/branch.sh +184 -0
- package/commands/cleanstash.sh +187 -0
- package/commands/cleanup.sh +452 -0
- package/commands/commit.sh +169 -0
- package/commands/commits.sh +211 -0
- package/commands/create.sh +205 -0
- package/commands/pr.sh +130 -0
- package/commands/stale.sh +403 -0
- package/commands/stale.zsh +403 -0
- package/commands/stash.sh +93 -0
- package/commands/stashes.sh +183 -0
- package/commands/status.sh +323 -0
- package/commands/switch.sh +174 -0
- package/commands/unstash.sh +174 -0
- package/commands/update.sh +191 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matthias Jäggli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# gitbash
|
|
2
|
+
|
|
3
|
+
Interactive git utilities for bash with fzf-powered menus.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Via npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g gitbash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run `gitbash --help` for more options.
|
|
14
|
+
|
|
15
|
+
### Shell Aliases
|
|
16
|
+
|
|
17
|
+
Add these aliases to your `.zshrc` or `.bashrc` for individual command access:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
alias branch="gitbash branch"
|
|
21
|
+
alias cleanstash="gitbash cleanstash"
|
|
22
|
+
alias cleanup="gitbash cleanup"
|
|
23
|
+
alias commit="gitbash commit"
|
|
24
|
+
alias commits="gitbash commits"
|
|
25
|
+
alias create="gitbash create"
|
|
26
|
+
alias pr="gitbash pr"
|
|
27
|
+
alias stale="gitbash stale"
|
|
28
|
+
alias stash="gitbash stash"
|
|
29
|
+
alias stashes="gitbash stashes"
|
|
30
|
+
alias status="gitbash status"
|
|
31
|
+
alias switch="gitbash switch"
|
|
32
|
+
alias unstash="gitbash unstash"
|
|
33
|
+
alias update="gitbash update"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or use the built-in shell integration, for all commands:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
eval "$(gitbash --init)"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Dependencies
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Required
|
|
46
|
+
brew install fzf
|
|
47
|
+
|
|
48
|
+
# Optional (recommended)
|
|
49
|
+
brew install git-delta # Better diff highlighting
|
|
50
|
+
brew install bat # File preview with syntax highlighting
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
### branch
|
|
56
|
+
|
|
57
|
+
Interactive menu for branch operations (create/switch/update).
|
|
58
|
+
|
|
59
|
+
### create
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
create [JIRA_LINK|ISSUE] [TITLE...]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Create feature branch with Jira parsing. Updates main first, pushes and tracks.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
create PROJ-123 fix login bug # → feature/PROJ-123-fix-login-bug
|
|
69
|
+
create # Interactive mode
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### switch
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
switch [FILTER...]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Switch branches with fzf. Shows local first, then remotes (deduped). Preview shows commit history.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
switch # Browse all branches
|
|
82
|
+
switch captcha # Pre-filter for "captcha"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### commit
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
commit [MESSAGE] [-p|--push] [-s|--staged]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Commit with optional push. Smart staging: prompts if both staged/unstaged exist.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
commit fix bug # Commit all
|
|
95
|
+
commit -s fix bug # Commit staged only
|
|
96
|
+
commit -p add feature # Commit and push
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### status
|
|
100
|
+
|
|
101
|
+
Interactive staging with fzf. Toggle files with Enter, ESC to exit. Shows diffs in preview.
|
|
102
|
+
|
|
103
|
+
### pr
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pr [-p|--push]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Open PR in browser. `-p` pushes first.
|
|
110
|
+
|
|
111
|
+
### update
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
update [-p|--push]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Merge latest main/master into current branch. Opens merge tool on conflicts.
|
|
118
|
+
|
|
119
|
+
### stale
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
stale [-m|--my] [--json] [FILTER...]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
List remote branches >3 months old (oldest first). Multi-select with TAB to delete.
|
|
126
|
+
|
|
127
|
+
- `Ctrl-A` toggles showing all branches
|
|
128
|
+
- `--my` filters by your git username
|
|
129
|
+
- `--json` outputs JSON for scripting (same format as cleanup)
|
|
130
|
+
|
|
131
|
+
### stashes
|
|
132
|
+
|
|
133
|
+
Interactive stash menu: create, apply, or delete stashes.
|
|
134
|
+
|
|
135
|
+
### stash
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
stash [NAME...]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Create named stash (includes untracked files).
|
|
142
|
+
|
|
143
|
+
### unstash
|
|
144
|
+
|
|
145
|
+
Apply stash with fzf picker. Optionally drop after applying.
|
|
146
|
+
|
|
147
|
+
### cleanstash
|
|
148
|
+
|
|
149
|
+
Delete stashes (multi-select with TAB).
|
|
150
|
+
|
|
151
|
+
### cleanup
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cleanup [--json]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Find and delete leftover local branches. Shows all local branches categorized:
|
|
158
|
+
|
|
159
|
+
- **[MERGED]** - Remote was deleted (pre-selected)
|
|
160
|
+
- **[STALE]** - No commits in 7+ days (pre-selected)
|
|
161
|
+
- **[RECENT]** - Recent activity (not selected)
|
|
162
|
+
|
|
163
|
+
Switches to main/master first if current branch is selected for deletion.
|
|
164
|
+
|
|
165
|
+
`--json` outputs non-interactive JSON for scripting:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
[
|
|
169
|
+
{
|
|
170
|
+
"last_change_timestamp": 1733123456,
|
|
171
|
+
"author_email": "dev@example.com",
|
|
172
|
+
"author_name": "Dev",
|
|
173
|
+
"name": "feature/old",
|
|
174
|
+
"last_change_relative": "2 weeks ago"
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### commits
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
commits [COUNT]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
List recent commits with option to revert. Multi-select with TAB to revert multiple.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
commits # Show last 20 commits
|
|
189
|
+
commits 50 # Show last 50 commits
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
See [LICENSE](LICENSE) file for details.
|
package/bin/gitbash
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gitbash - Interactive git utilities for bash with fzf-powered menus
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
COMMANDS_DIR="$SCRIPT_DIR/commands"
|
|
8
|
+
|
|
9
|
+
# Available commands
|
|
10
|
+
COMMANDS=(
|
|
11
|
+
"branch"
|
|
12
|
+
"cleanstash"
|
|
13
|
+
"cleanup"
|
|
14
|
+
"commit"
|
|
15
|
+
"commits"
|
|
16
|
+
"create"
|
|
17
|
+
"pr"
|
|
18
|
+
"stale"
|
|
19
|
+
"stash"
|
|
20
|
+
"stashes"
|
|
21
|
+
"status"
|
|
22
|
+
"switch"
|
|
23
|
+
"unstash"
|
|
24
|
+
"update"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
show_help() {
|
|
28
|
+
cat << 'EOF'
|
|
29
|
+
gitbash - Interactive git utilities for bash
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
gitbash <command> [args...]
|
|
33
|
+
gitbash --help
|
|
34
|
+
gitbash --init
|
|
35
|
+
|
|
36
|
+
Commands:
|
|
37
|
+
branch Interactive menu for branch operations
|
|
38
|
+
cleanstash Delete stashes (multi-select)
|
|
39
|
+
cleanup Find and delete leftover local branches
|
|
40
|
+
commit Commit with optional push
|
|
41
|
+
commits List recent commits with revert option
|
|
42
|
+
create Create feature branch with Jira parsing
|
|
43
|
+
pr Open PR in browser
|
|
44
|
+
stale List stale remote branches
|
|
45
|
+
stash Create named stash
|
|
46
|
+
stashes Interactive stash menu
|
|
47
|
+
status Interactive staging with fzf
|
|
48
|
+
switch Switch branches with fzf
|
|
49
|
+
unstash Apply stash with fzf picker
|
|
50
|
+
update Merge latest main/master into current branch
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-h, --help Show this help message
|
|
54
|
+
--init Print shell initialization code (for sourcing)
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
gitbash commit fix bug -p # Commit and push
|
|
58
|
+
gitbash switch feature # Switch to branch containing "feature"
|
|
59
|
+
gitbash create PROJ-123 fix # Create feature/PROJ-123-fix
|
|
60
|
+
|
|
61
|
+
Shell Integration:
|
|
62
|
+
Add to your .bashrc or .bash_profile:
|
|
63
|
+
eval "$(gitbash --init)"
|
|
64
|
+
|
|
65
|
+
This enables calling commands directly:
|
|
66
|
+
commit fix bug -p
|
|
67
|
+
switch feature
|
|
68
|
+
|
|
69
|
+
Dependencies:
|
|
70
|
+
brew install fzf # Required
|
|
71
|
+
brew install git-delta # Optional: better diffs
|
|
72
|
+
brew install bat # Optional: syntax highlighting
|
|
73
|
+
EOF
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
show_init() {
|
|
77
|
+
cat << EOF
|
|
78
|
+
# gitbash shell integration
|
|
79
|
+
# Add this to your .bashrc or .bash_profile:
|
|
80
|
+
# eval "\$(gitbash --init)"
|
|
81
|
+
|
|
82
|
+
GITBASH_COMMANDS_DIR="$COMMANDS_DIR"
|
|
83
|
+
|
|
84
|
+
EOF
|
|
85
|
+
# Output source commands for each script
|
|
86
|
+
for cmd in "${COMMANDS[@]}"; do
|
|
87
|
+
if [[ -f "$COMMANDS_DIR/$cmd.sh" ]]; then
|
|
88
|
+
echo "source \"\$GITBASH_COMMANDS_DIR/$cmd.sh\""
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Main entry point
|
|
94
|
+
main() {
|
|
95
|
+
case "${1:-}" in
|
|
96
|
+
-h|--help|"")
|
|
97
|
+
show_help
|
|
98
|
+
exit 0
|
|
99
|
+
;;
|
|
100
|
+
--init)
|
|
101
|
+
show_init
|
|
102
|
+
exit 0
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
local cmd="$1"
|
|
106
|
+
shift
|
|
107
|
+
|
|
108
|
+
# Check if command exists
|
|
109
|
+
if [[ -f "$COMMANDS_DIR/$cmd.sh" ]]; then
|
|
110
|
+
# Source the command file and execute the function
|
|
111
|
+
source "$COMMANDS_DIR/$cmd.sh"
|
|
112
|
+
"$cmd" "$@"
|
|
113
|
+
else
|
|
114
|
+
echo "Error: Unknown command '$cmd'" >&2
|
|
115
|
+
echo "Run 'gitbash --help' for available commands." >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main "$@"
|
|
123
|
+
|
|
124
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
branch() {
|
|
2
|
+
# -----------------------------
|
|
3
|
+
# 0. Check for help flag
|
|
4
|
+
# -----------------------------
|
|
5
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
6
|
+
cat << 'EOF'
|
|
7
|
+
Usage: branch [OPTIONS]
|
|
8
|
+
|
|
9
|
+
Interactive menu to manage git branches.
|
|
10
|
+
Choose between creating, switching, or updating branches.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-h, --help Show this help message
|
|
14
|
+
|
|
15
|
+
Features:
|
|
16
|
+
- Interactive action selection
|
|
17
|
+
- Create: Create a new branch with Jira issue parsing
|
|
18
|
+
- Switch: Switch between branches with fzf selector
|
|
19
|
+
- Update: Update current branch with latest main/master
|
|
20
|
+
|
|
21
|
+
Navigation:
|
|
22
|
+
↑/↓ or j/k Navigate through options
|
|
23
|
+
Enter Select action
|
|
24
|
+
ESC/Ctrl-C Abort
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ branch
|
|
28
|
+
Branch action >
|
|
29
|
+
> 🌿 Create - Create a new feature branch
|
|
30
|
+
🔀 Switch - Switch to another branch
|
|
31
|
+
⬆️ Update - Update current branch with main/master
|
|
32
|
+
✖ Abort
|
|
33
|
+
|
|
34
|
+
# Select "Create" and press Enter
|
|
35
|
+
Enter Jira link (e.g., https://jira.company.com/browse/PROJ-123):
|
|
36
|
+
> PROJ-123
|
|
37
|
+
Parsed issue number: PROJ-123
|
|
38
|
+
|
|
39
|
+
Enter branch title (will be converted to lowercase with dashes):
|
|
40
|
+
> add new feature
|
|
41
|
+
|
|
42
|
+
Branch name: feature/PROJ-123-add-new-feature
|
|
43
|
+
Updating 'main' from origin...
|
|
44
|
+
✓ 'main' is up to date.
|
|
45
|
+
✓ Successfully created and switched to branch: feature/PROJ-123-add-new-feature (from main)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
$ branch
|
|
50
|
+
Branch action >
|
|
51
|
+
> 🌿 Create - Create a new feature branch
|
|
52
|
+
🔀 Switch - Switch to another branch
|
|
53
|
+
⬆️ Update - Update current branch with main/master
|
|
54
|
+
✖ Abort
|
|
55
|
+
|
|
56
|
+
# Select "Switch" and press Enter
|
|
57
|
+
Select branch:
|
|
58
|
+
Current: main
|
|
59
|
+
> local: main
|
|
60
|
+
local: feature/PROJ-123-add-new-feature
|
|
61
|
+
remote: origin/develop
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
$ branch
|
|
66
|
+
Branch action >
|
|
67
|
+
> 🌿 Create - Create a new feature branch
|
|
68
|
+
🔀 Switch - Switch to another branch
|
|
69
|
+
⬆️ Update - Update current branch with main/master
|
|
70
|
+
✖ Abort
|
|
71
|
+
|
|
72
|
+
# Select "Update" and press Enter
|
|
73
|
+
Fetching latest 'main' from origin...
|
|
74
|
+
Merging updated 'main' into 'feature/PROJ-123-add-new-feature'...
|
|
75
|
+
✓ 'feature/PROJ-123-add-new-feature' is now up-to-date with 'main'.
|
|
76
|
+
|
|
77
|
+
Actions:
|
|
78
|
+
🌿 Create - Create a new feature branch with Jira parsing
|
|
79
|
+
🔀 Switch - Switch to another branch using fzf
|
|
80
|
+
⬆️ Update - Update current branch with latest main/master
|
|
81
|
+
|
|
82
|
+
Requirements:
|
|
83
|
+
- fzf (fuzzy finder) - will prompt to install if not found
|
|
84
|
+
- create, switch, and update functions must be available
|
|
85
|
+
|
|
86
|
+
See also:
|
|
87
|
+
create -h Show help for create command
|
|
88
|
+
switch -h Show help for switch command
|
|
89
|
+
update -h Show help for update command
|
|
90
|
+
|
|
91
|
+
EOF
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# -----------------------------
|
|
96
|
+
# 1. Check for fzf
|
|
97
|
+
# -----------------------------
|
|
98
|
+
if ! command -v fzf >/dev/null 2>&1; then
|
|
99
|
+
echo "fzf is not installed."
|
|
100
|
+
|
|
101
|
+
# Ask whether to install with Homebrew
|
|
102
|
+
read -rp "Install fzf with Homebrew? (y/N): " ans
|
|
103
|
+
case "$ans" in
|
|
104
|
+
[yY][eE][sS]|[yY])
|
|
105
|
+
echo "Installing fzf with brew..."
|
|
106
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
107
|
+
echo "Homebrew is not installed. Aborting."
|
|
108
|
+
return 1
|
|
109
|
+
fi
|
|
110
|
+
brew install fzf || { echo "fzf install failed. Aborting."; return 1; }
|
|
111
|
+
# Optional: install shell integrations automatically
|
|
112
|
+
if [[ -f "$(brew --prefix)/opt/fzf/install" ]]; then
|
|
113
|
+
echo "Running fzf install script..."
|
|
114
|
+
yes | "$(brew --prefix)/opt/fzf/install"
|
|
115
|
+
fi
|
|
116
|
+
;;
|
|
117
|
+
*)
|
|
118
|
+
echo "Aborted (skipped fzf installation)."
|
|
119
|
+
return 1
|
|
120
|
+
;;
|
|
121
|
+
esac
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# -----------------------------
|
|
125
|
+
# 2. Build action menu
|
|
126
|
+
# -----------------------------
|
|
127
|
+
local create_option="🌿 Create - Create a new feature branch"
|
|
128
|
+
local switch_option="🔀 Switch - Switch to another branch"
|
|
129
|
+
local update_option="⬆️ Update - Update current branch with main/master"
|
|
130
|
+
local abort_label="✖ Abort"
|
|
131
|
+
|
|
132
|
+
local choices
|
|
133
|
+
choices=$(
|
|
134
|
+
{
|
|
135
|
+
echo "$create_option"
|
|
136
|
+
echo "$switch_option"
|
|
137
|
+
echo "$update_option"
|
|
138
|
+
echo "$abort_label"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# -----------------------------
|
|
143
|
+
# 3. Run fzf menu
|
|
144
|
+
# -----------------------------
|
|
145
|
+
local selection
|
|
146
|
+
selection=$(printf "%s\n" "$choices" \
|
|
147
|
+
| fzf --prompt="Branch action > " \
|
|
148
|
+
-i \
|
|
149
|
+
--reverse \
|
|
150
|
+
--border \
|
|
151
|
+
--header="What would you like to do?" \
|
|
152
|
+
--no-multi \
|
|
153
|
+
--bind=enter:accept
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# ESC or Ctrl-C
|
|
157
|
+
if [[ -z "$selection" ]]; then
|
|
158
|
+
echo "Aborted."
|
|
159
|
+
return 1
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# -----------------------------
|
|
163
|
+
# 4. Execute based on selection
|
|
164
|
+
# -----------------------------
|
|
165
|
+
case "$selection" in
|
|
166
|
+
"$create_option")
|
|
167
|
+
create
|
|
168
|
+
;;
|
|
169
|
+
"$switch_option")
|
|
170
|
+
switch
|
|
171
|
+
;;
|
|
172
|
+
"$update_option")
|
|
173
|
+
update
|
|
174
|
+
;;
|
|
175
|
+
"$abort_label")
|
|
176
|
+
echo "Aborted."
|
|
177
|
+
return 1
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
echo "Unknown selection."
|
|
181
|
+
return 1
|
|
182
|
+
;;
|
|
183
|
+
esac
|
|
184
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
cleanstash() {
|
|
2
|
+
# -----------------------------
|
|
3
|
+
# 0. Check for help flag
|
|
4
|
+
# -----------------------------
|
|
5
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
6
|
+
cat << 'EOF'
|
|
7
|
+
Usage: cleanstash [OPTIONS]
|
|
8
|
+
|
|
9
|
+
Delete git stashes without applying them.
|
|
10
|
+
Interactive fuzzy finder (fzf) with preview and multi-select support.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-h, --help Show this help message
|
|
14
|
+
|
|
15
|
+
Features:
|
|
16
|
+
- Multi-select support (use TAB to select multiple stashes)
|
|
17
|
+
- Preview stash contents before deletion
|
|
18
|
+
- Confirmation prompt before deletion
|
|
19
|
+
- Safe deletion (deletes in reverse order to maintain indices)
|
|
20
|
+
|
|
21
|
+
Navigation:
|
|
22
|
+
↑/↓ or j/k Navigate through stashes
|
|
23
|
+
TAB Select/deselect stash (multi-select)
|
|
24
|
+
Enter Confirm selection
|
|
25
|
+
ESC/Ctrl-C Abort
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
$ cleanstash
|
|
29
|
+
Available stashes >
|
|
30
|
+
> stash@{0}: WIP on main: 1a2b3c4 commit message
|
|
31
|
+
stash@{1}: WIP on feature: 5d6e7f8 another commit
|
|
32
|
+
stash@{2}: On dev: 9a8b7c6 old work
|
|
33
|
+
✖ Abort
|
|
34
|
+
|
|
35
|
+
# Select stash@{0} and stash@{2} using TAB, then press Enter
|
|
36
|
+
|
|
37
|
+
Selected stashes to delete:
|
|
38
|
+
- stash@{0}
|
|
39
|
+
- stash@{2}
|
|
40
|
+
|
|
41
|
+
Delete these 2 stash(es)? (y/N): y
|
|
42
|
+
Deleting stashes...
|
|
43
|
+
Dropping stash@{2} ...
|
|
44
|
+
✓ Deleted stash@{2}
|
|
45
|
+
Dropping stash@{0} ...
|
|
46
|
+
✓ Deleted stash@{0}
|
|
47
|
+
|
|
48
|
+
Requirements:
|
|
49
|
+
- fzf (fuzzy finder) - will prompt to install if not found
|
|
50
|
+
- git stash list with at least one stash
|
|
51
|
+
|
|
52
|
+
Notes:
|
|
53
|
+
- Stashes are deleted in reverse order to prevent index shifts
|
|
54
|
+
- Preview shows the full diff of the selected stash
|
|
55
|
+
- No changes are applied to your working directory
|
|
56
|
+
|
|
57
|
+
EOF
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# -----------------------------
|
|
62
|
+
# 1. Check for fzf
|
|
63
|
+
# -----------------------------
|
|
64
|
+
if ! command -v fzf >/dev/null 2>&1; then
|
|
65
|
+
echo "fzf is not installed."
|
|
66
|
+
|
|
67
|
+
# Ask whether to install with Homebrew
|
|
68
|
+
read -r -p "Install fzf with Homebrew? (y/N): " ans
|
|
69
|
+
case "$ans" in
|
|
70
|
+
[yY][eE][sS]|[yY])
|
|
71
|
+
echo "Installing fzf with brew..."
|
|
72
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
73
|
+
echo "Homebrew is not installed. Aborting."
|
|
74
|
+
return 1
|
|
75
|
+
fi
|
|
76
|
+
brew install fzf || { echo "fzf install failed. Aborting."; return 1; }
|
|
77
|
+
# Optional: install shell integrations automatically
|
|
78
|
+
if [[ -f "$(brew --prefix)/opt/fzf/install" ]]; then
|
|
79
|
+
echo "Running fzf install script..."
|
|
80
|
+
yes | "$(brew --prefix)/opt/fzf/install"
|
|
81
|
+
fi
|
|
82
|
+
;;
|
|
83
|
+
*)
|
|
84
|
+
echo "Aborted (skipped fzf installation)."
|
|
85
|
+
return 1
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# -----------------------------
|
|
91
|
+
# 2. Build menu entries
|
|
92
|
+
# -----------------------------
|
|
93
|
+
local abort_label="✖ Abort"
|
|
94
|
+
|
|
95
|
+
local choices
|
|
96
|
+
choices=$(
|
|
97
|
+
{
|
|
98
|
+
git stash list
|
|
99
|
+
echo "$abort_label"
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# -----------------------------
|
|
104
|
+
# 3. Run fzf full-screen picker with preview (multi-select)
|
|
105
|
+
# -----------------------------
|
|
106
|
+
local selection
|
|
107
|
+
selection=$(printf "%s\n" "$choices" \
|
|
108
|
+
| fzf --prompt="Available stashes > " \
|
|
109
|
+
-i \
|
|
110
|
+
--reverse \
|
|
111
|
+
--border \
|
|
112
|
+
--header="Select stashes to delete (TAB for multi-select)" \
|
|
113
|
+
--multi \
|
|
114
|
+
--bind=enter:accept \
|
|
115
|
+
--preview='
|
|
116
|
+
sel=$(echo {} | grep -o "stash@{[0-9]\+}" || true);
|
|
117
|
+
if [[ -n "$sel" ]]; then
|
|
118
|
+
git stash show -p "$sel";
|
|
119
|
+
else
|
|
120
|
+
echo "No preview";
|
|
121
|
+
fi
|
|
122
|
+
'
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# ESC or Ctrl-C
|
|
126
|
+
if [[ -z "$selection" ]]; then
|
|
127
|
+
echo "Aborted."
|
|
128
|
+
return 1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Abort option
|
|
132
|
+
if echo "$selection" | grep -q "^$abort_label$"; then
|
|
133
|
+
echo "Aborted."
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# -----------------------------
|
|
138
|
+
# 4. Extract stash IDs safely
|
|
139
|
+
# -----------------------------
|
|
140
|
+
local stash_ids=()
|
|
141
|
+
while IFS= read -r line; do
|
|
142
|
+
local stash_id
|
|
143
|
+
stash_id=$(echo "$line" | grep -o 'stash@{[0-9]\+}')
|
|
144
|
+
if [[ -n "$stash_id" ]]; then
|
|
145
|
+
stash_ids+=("$stash_id")
|
|
146
|
+
fi
|
|
147
|
+
done <<< "$selection"
|
|
148
|
+
|
|
149
|
+
if [[ ${#stash_ids[@]} -eq 0 ]]; then
|
|
150
|
+
echo "Error: Could not determine any stash IDs."
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# -----------------------------
|
|
155
|
+
# 5. Confirm before deletion
|
|
156
|
+
# -----------------------------
|
|
157
|
+
echo "Selected stashes to delete:"
|
|
158
|
+
for stash_id in "${stash_ids[@]}"; do
|
|
159
|
+
echo " - $stash_id"
|
|
160
|
+
done
|
|
161
|
+
echo
|
|
162
|
+
|
|
163
|
+
read -rp "Delete these ${#stash_ids[@]} stash(es)? (y/N): " confirm_ans
|
|
164
|
+
case "$confirm_ans" in
|
|
165
|
+
[yY][eE][sS]|[yY])
|
|
166
|
+
# -----------------------------
|
|
167
|
+
# 6. Delete stashes (in reverse order to maintain indices)
|
|
168
|
+
# -----------------------------
|
|
169
|
+
echo "Deleting stashes..."
|
|
170
|
+
# Sort by stash number in descending order to delete safely
|
|
171
|
+
local sorted_ids
|
|
172
|
+
mapfile -t sorted_ids < <(printf '%s\n' "${stash_ids[@]}" | sort -t'{' -k2 -rn)
|
|
173
|
+
|
|
174
|
+
for stash_id in "${sorted_ids[@]}"; do
|
|
175
|
+
echo "Dropping $stash_id ..."
|
|
176
|
+
if git stash drop "$stash_id"; then
|
|
177
|
+
echo "✓ Deleted $stash_id"
|
|
178
|
+
else
|
|
179
|
+
echo "✗ Failed to delete $stash_id"
|
|
180
|
+
fi
|
|
181
|
+
done
|
|
182
|
+
;;
|
|
183
|
+
*)
|
|
184
|
+
echo "Deletion cancelled. No stashes were removed."
|
|
185
|
+
;;
|
|
186
|
+
esac
|
|
187
|
+
}
|